mirror of
https://github.com/Jackett/Jackett.git
synced 2025-12-21 21:57:09 +01:00
Compare commits
114 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
c2f11306b1 | ||
|
|
07a0c2c828 | ||
|
|
28eaa637df | ||
|
|
8707e6b2e9 | ||
|
|
7a8d83b693 | ||
|
|
403c0ef7e4 | ||
|
|
3374c14311 | ||
|
|
9eade51d89 | ||
|
|
bc9e4a30cb | ||
|
|
0103c48c97 | ||
|
|
29ecf8a584 | ||
|
|
eec07bc5b8 | ||
|
|
bf23878477 | ||
|
|
043085e8f3 | ||
|
|
39e612d60c | ||
|
|
6f8b1b749d | ||
|
|
439e9296f9 | ||
|
|
1332b49370 | ||
|
|
33a7db5ec4 | ||
|
|
2075e914eb | ||
|
|
758ad91a55 | ||
|
|
e9b604d3c4 | ||
|
|
51aa4f35bd | ||
|
|
fc55882f16 | ||
|
|
d03cbefa57 | ||
|
|
e079c90535 | ||
|
|
8591add0bf | ||
|
|
fbbe4f9c45 | ||
|
|
289c5cd24f | ||
|
|
84a45737d3 | ||
|
|
d083cf774a | ||
|
|
f2ce167bbf | ||
|
|
4e04bbbcf4 | ||
|
|
7f5d00e89f | ||
|
|
f777114644 | ||
|
|
7c63a6b8f2 | ||
|
|
f0140999bf | ||
|
|
6f0a249503 | ||
|
|
2e79500f50 | ||
|
|
720b5971d3 | ||
|
|
dba63857e4 | ||
|
|
b05ee653d3 | ||
|
|
84df60368c | ||
|
|
76102ac171 | ||
|
|
3c6e77a2ca | ||
|
|
f6272032a6 | ||
|
|
41fb5e89b4 | ||
|
|
42c9967844 | ||
|
|
e740e2434d | ||
|
|
057df28d1b | ||
|
|
6fbc4b6904 | ||
|
|
cab608f0ec | ||
|
|
f5592d04e2 | ||
|
|
5138496436 | ||
|
|
bcc1e5ff6c | ||
|
|
7eb57d8e9a | ||
|
|
929c12ccc6 | ||
|
|
1ba1b91b8e | ||
|
|
d90a2613fc | ||
|
|
0d25ed2921 | ||
|
|
a07bffa773 | ||
|
|
f0da6ce247 | ||
|
|
32d0a8d703 | ||
|
|
6e2087d5dc | ||
|
|
82f330f4af | ||
|
|
14f86107e1 | ||
|
|
845faf9066 | ||
|
|
7c2b801ee9 | ||
|
|
f96dca5653 | ||
|
|
0e4e9f4253 | ||
|
|
dc38f8c041 | ||
|
|
17f544be36 | ||
|
|
fb59e84def | ||
|
|
de8e33e647 | ||
|
|
cf1bbc603e | ||
|
|
ecc60e59d8 | ||
|
|
e7be6faf2f | ||
|
|
9e3076dde6 | ||
|
|
35103206cf | ||
|
|
29cf00560f | ||
|
|
9ef9302808 | ||
|
|
616b436648 | ||
|
|
702e975d57 | ||
|
|
6c90016c0c | ||
|
|
90152a7eed | ||
|
|
dabd95655b | ||
|
|
4f938e3ea8 | ||
|
|
f21a721ddb | ||
|
|
7d65e60750 | ||
|
|
c87f1b3949 | ||
|
|
08b471fd1e | ||
|
|
4a0d2dcc57 | ||
|
|
91eae526f9 | ||
|
|
11fd2db5a5 | ||
|
|
75e7ce81c2 | ||
|
|
345602926e | ||
|
|
780ea4c631 | ||
|
|
cf9d87a7d8 | ||
|
|
3f2d6f0cee | ||
|
|
22cf450d07 | ||
|
|
8bd7233756 | ||
|
|
2807a71e0e | ||
|
|
b60bcda109 | ||
|
|
4d36165cdf | ||
|
|
44de6e5459 | ||
|
|
d31c593d4b | ||
|
|
0473029277 | ||
|
|
3bef19cbfe | ||
|
|
79fc4850ed | ||
|
|
7cf24e906a | ||
|
|
9870d38cbb | ||
|
|
994604271b | ||
|
|
81f2c7e91c | ||
|
|
b0788e491e |
29
README.md
29
README.md
@@ -2,7 +2,7 @@
|
||||
|
||||
[](https://github.com/Jackett/Jackett/issues)
|
||||
[](https://github.com/Jackett/Jackett/pulls)
|
||||
[](https://ci.appveyor.com/project/camjac251/jackett)
|
||||
[](https://ci.appveyor.com/project/camjac251/jackett)
|
||||
[](https://github.com/Jackett/Jackett/releases/latest)
|
||||
[](https://hub.docker.com/r/linuxserver/jackett/)
|
||||
|
||||
@@ -20,6 +20,8 @@ Developer note: The software implements the [Torznab](https://github.com/Sonarr/
|
||||
|
||||
### Supported Public Trackers
|
||||
* Anidex
|
||||
* Anime Tosho
|
||||
* cpasbien
|
||||
* EZTV
|
||||
* Horrible Subs
|
||||
* Il Corsaro Nero <!-- maintained by bonny1992 -->
|
||||
@@ -27,6 +29,7 @@ Developer note: The software implements the [Torznab](https://github.com/Sonarr/
|
||||
* KickAssTorrent
|
||||
* KickAssTorrent (kat.how clone)
|
||||
* LimeTorrents
|
||||
* NextTorrent
|
||||
* Nyaa.si
|
||||
* Nyaa-Pantsu
|
||||
* Nyoo
|
||||
@@ -36,9 +39,11 @@ Developer note: The software implements the [Torznab](https://github.com/Sonarr/
|
||||
* The Pirate Bay
|
||||
* TNTVillage <!-- maintained by bonny1992 -->
|
||||
* Tokyo Toshokan
|
||||
* Torrent Downloads
|
||||
* TorrentProject
|
||||
* Torrentz2
|
||||
|
||||
* zetorrents
|
||||
|
||||
### Supported Private Trackers
|
||||
* 2 Fast 4 You
|
||||
* 3D Torrents
|
||||
@@ -57,6 +62,7 @@ Developer note: The software implements the [Torznab](https://github.com/Sonarr/
|
||||
* Audiobook Torrents
|
||||
* Awesome-HD
|
||||
* Avistaz
|
||||
* B2S-Share
|
||||
* BakaBT [![(invite needed)][inviteneeded]](#)
|
||||
* bB
|
||||
* Best Friends
|
||||
@@ -71,6 +77,7 @@ Developer note: The software implements the [Torznab](https://github.com/Sonarr/
|
||||
* Bitspyder
|
||||
* Blu-bits
|
||||
* BlueBird
|
||||
* Blutopia
|
||||
* BroadcastTheNet [![(invite needed)][inviteneeded]](#)
|
||||
* BrokenStones
|
||||
* BTNext
|
||||
@@ -139,7 +146,8 @@ Developer note: The software implements the [Torznab](https://github.com/Sonarr/
|
||||
* LosslessClub
|
||||
* M-Team - TP
|
||||
* Magico
|
||||
* Majomparádé
|
||||
* Majomparádé
|
||||
* Manicomio Share
|
||||
* Mononoké-BT
|
||||
* MoreThanTV
|
||||
* MyAnonamouse
|
||||
@@ -197,7 +205,6 @@ Developer note: The software implements the [Torznab](https://github.com/Sonarr/
|
||||
* TenYardTracker
|
||||
* Torrent Network
|
||||
* Torrent Sector Crew
|
||||
* Torrent411
|
||||
* Torrent9
|
||||
* TorrentBD
|
||||
* TorrentBytes
|
||||
@@ -214,21 +221,25 @@ Developer note: The software implements the [Torznab](https://github.com/Sonarr/
|
||||
* Trezzor
|
||||
* TV Chaos UK
|
||||
* TV-Vault
|
||||
* T411 v2
|
||||
* u-Torrent
|
||||
* UHDBits
|
||||
* Ultimate Gamer Club
|
||||
* ULTRAHDCLUB
|
||||
* Waffles
|
||||
* World-In-HD [![(invite needed)][inviteneeded]](#)
|
||||
* World-In-HD
|
||||
* WorldOfP2P
|
||||
* x264
|
||||
* XSpeeds
|
||||
* Xthor
|
||||
* Xtreme Zone
|
||||
* Xtreme Zone
|
||||
* YggTorrent
|
||||
* Zamunda.net
|
||||
* Zelka.org
|
||||
* Ztracker
|
||||
|
||||
Trackers marked with [![(invite needed)][inviteneeded]](#) have no active maintainer and are missing features or are broken. If you have an invite for them please send it to kaso1717 -at- gmail.com to get them fixed/improved.
|
||||
|
||||
|
||||
## Installation on Windows
|
||||
|
||||
We recommend you install Jackett as a Windows service using the supplied installer. You may also download the zipped version if you would like to configure everything manually.
|
||||
@@ -244,7 +255,7 @@ To get started with using the installer for Jackett, follow the steps below:
|
||||
|
||||
When installed as a service the tray icon acts as a way to open/start/stop Jackett. If you opted to not install it as a service then Jackett will run its web server from the tray tool.
|
||||
|
||||
Jackett can also be run from the command line if you would like to see log messages (Ensure the server isn't already running from the tray/service). This can be done by using "JackettConsole.exe" (for Command Prompt), found in the Jackett data folder: "%ProgramData%\Jackett".
|
||||
Jackett can also be run from the command line if you would like to see log messages (Ensure the server isn't already running from the tray/service). This can be done by using "JackettConsole.exe" (for Command Prompt), found in the Jackett data folder: "%ProgramData%\Jackett".
|
||||
|
||||
## Installation on Linux
|
||||
1. Install [Mono 4](http://www.mono-project.com/download/#download-lin) or better (version 4.8 is recommended)
|
||||
@@ -271,7 +282,7 @@ Detailed instructions for [Ubuntu 14.x](http://www.htpcguides.com/install-jacket
|
||||
Detailed instructions are available at [LinuxServer.io Jackett Docker](https://hub.docker.com/r/linuxserver/jackett/). The Jackett Docker is highly recommended, especially if you are having Mono stability issues or having issues running Mono on your system eg. QNAP, Synology. Thanks to [LinuxServer.io](https://linuxserver.io)
|
||||
|
||||
## Installation on Synology
|
||||
Jackett is available as beta package from [SynoCommuniy](https://synocommunity.com/)
|
||||
Jackett is available as beta package from [SynoCommunity](https://synocommunity.com/)
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
|
||||
@@ -94,8 +94,14 @@
|
||||
<MonoDevelop>
|
||||
<Properties>
|
||||
<Policies>
|
||||
<TextStylePolicy inheritsSet="VisualStudio" inheritsScope="text/plain" scope="text/x-csharp" />
|
||||
<CSharpFormattingPolicy IndentSwitchBody="True" IndentBlocksInsideExpressions="True" AnonymousMethodBraceStyle="NextLine" PropertyBraceStyle="NextLine" PropertyGetBraceStyle="NextLine" PropertySetBraceStyle="NextLine" EventBraceStyle="NextLine" EventAddBraceStyle="NextLine" EventRemoveBraceStyle="NextLine" StatementBraceStyle="NextLine" ElseNewLinePlacement="NewLine" CatchNewLinePlacement="NewLine" FinallyNewLinePlacement="NewLine" WhileNewLinePlacement="DoNotCare" ArrayInitializerWrapping="DoNotChange" ArrayInitializerBraceStyle="NextLine" BeforeMethodDeclarationParentheses="False" BeforeMethodCallParentheses="False" BeforeConstructorDeclarationParentheses="False" NewLineBeforeConstructorInitializerColon="NewLine" NewLineAfterConstructorInitializerColon="SameLine" BeforeDelegateDeclarationParentheses="False" NewParentheses="False" SpacesBeforeBrackets="False" inheritsSet="Mono" inheritsScope="text/x-csharp" scope="text/x-csharp" />
|
||||
<TextStylePolicy TabWidth="4" IndentWidth="4" RemoveTrailingWhitespace="True" NoTabsAfterNonTabs="False" EolMarker="Native" FileWidth="80" TabsToSpaces="True" scope="text/x-csharp">
|
||||
<inheritsSet />
|
||||
<inheritsScope />
|
||||
</TextStylePolicy>
|
||||
<CSharpFormattingPolicy IndentBlock="True" IndentBraces="False" IndentSwitchSection="False" IndentSwitchCaseSection="True" LabelPositioning="OneLess" NewLinesForBracesInTypes="True" NewLinesForBracesInMethods="True" NewLinesForBracesInProperties="False" NewLinesForBracesInAccessors="False" NewLinesForBracesInAnonymousMethods="False" NewLinesForBracesInControlBlocks="False" NewLinesForBracesInAnonymousTypes="False" NewLinesForBracesInObjectCollectionArrayInitializers="False" NewLinesForBracesInLambdaExpressionBody="False" NewLineForElse="False" NewLineForCatch="False" NewLineForFinally="False" NewLineForMembersInObjectInit="False" NewLineForMembersInAnonymousTypes="False" NewLineForClausesInQuery="False" SpacingAfterMethodDeclarationName="True" SpaceWithinMethodDeclarationParenthesis="False" SpaceBetweenEmptyMethodDeclarationParentheses="False" SpaceAfterMethodCallName="True" SpaceWithinMethodCallParentheses="False" SpaceBetweenEmptyMethodCallParentheses="False" SpaceAfterControlFlowStatementKeyword="True" SpaceWithinExpressionParentheses="False" SpaceWithinCastParentheses="False" SpaceWithinOtherParentheses="False" SpaceAfterCast="False" SpacesIgnoreAroundVariableDeclaration="False" SpaceBeforeOpenSquareBracket="True" SpaceBetweenEmptySquareBrackets="False" SpaceWithinSquareBrackets="False" SpaceAfterColonInBaseTypeDeclaration="True" SpaceAfterComma="True" SpaceAfterDot="False" SpaceAfterSemicolonsInForStatement="True" SpaceBeforeColonInBaseTypeDeclaration="True" SpaceBeforeComma="False" SpaceBeforeDot="False" SpaceBeforeSemicolonsInForStatement="False" SpacingAroundBinaryOperator="Single" WrappingPreserveSingleLine="True" WrappingKeepStatementsOnSingleLine="True" PlaceSystemDirectiveFirst="True" scope="text/x-csharp">
|
||||
<inheritsSet />
|
||||
<inheritsScope />
|
||||
</CSharpFormattingPolicy>
|
||||
</Policies>
|
||||
</Properties>
|
||||
</MonoDevelop>
|
||||
|
||||
@@ -28,7 +28,12 @@ namespace JackettTest
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
public void InitIndexers()
|
||||
public IWebIndexer GetWebIndexer(string name)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
public void InitIndexers(IEnumerable<string> path)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
@@ -43,16 +48,6 @@ namespace JackettTest
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
public void InitCardigannIndexers(string path)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
public void SortIndexers()
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
public void InitAggregateIndexer()
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
|
||||
@@ -76,7 +76,7 @@ namespace Jackett.Updater
|
||||
#endif
|
||||
*/
|
||||
}
|
||||
catch (ArgumentException e)
|
||||
catch (ArgumentException)
|
||||
{
|
||||
Engine.Logger.Info("Process " + pid.ToString() + " is already dead");
|
||||
}
|
||||
@@ -185,6 +185,7 @@ namespace Jackett.Updater
|
||||
"Definitions/rarbg.yml",
|
||||
"Definitions/t411.yml",
|
||||
"Definitions/hdbc.yml", // renamed to hdbitscom
|
||||
"Definitions/maniatorrent.yml",
|
||||
"Definitions/nyaa.yml",
|
||||
"Definitions/nachtwerk.yml",
|
||||
};
|
||||
|
||||
@@ -66,9 +66,9 @@ Global
|
||||
GlobalSection(MonoDevelopProperties) = preSolution
|
||||
Policies = $0
|
||||
$0.TextStylePolicy = $1
|
||||
$1.inheritsSet = VisualStudio
|
||||
$1.inheritsScope = text/plain
|
||||
$1.scope = text/x-csharp
|
||||
$1.TabsToSpaces = True
|
||||
$1.EolMarker = Unix
|
||||
$0.CSharpFormattingPolicy = $2
|
||||
$2.IndentSwitchBody = True
|
||||
$2.IndentBlocksInsideExpressions = True
|
||||
@@ -94,8 +94,27 @@ Global
|
||||
$2.BeforeDelegateDeclarationParentheses = False
|
||||
$2.NewParentheses = False
|
||||
$2.SpacesBeforeBrackets = False
|
||||
$2.inheritsSet = Mono
|
||||
$2.inheritsScope = text/x-csharp
|
||||
$2.scope = text/x-csharp
|
||||
$2.IndentSwitchSection = True
|
||||
$2.NewLinesForBracesInProperties = True
|
||||
$2.NewLinesForBracesInAccessors = True
|
||||
$2.NewLinesForBracesInAnonymousMethods = True
|
||||
$2.NewLinesForBracesInControlBlocks = True
|
||||
$2.NewLinesForBracesInAnonymousTypes = True
|
||||
$2.NewLinesForBracesInObjectCollectionArrayInitializers = True
|
||||
$2.NewLinesForBracesInLambdaExpressionBody = True
|
||||
$2.NewLineForElse = True
|
||||
$2.NewLineForCatch = True
|
||||
$2.NewLineForFinally = True
|
||||
$2.NewLineForMembersInObjectInit = True
|
||||
$2.NewLineForMembersInAnonymousTypes = True
|
||||
$2.NewLineForClausesInQuery = True
|
||||
$2.SpacingAfterMethodDeclarationName = False
|
||||
$2.SpaceAfterMethodCallName = False
|
||||
$2.SpaceBeforeOpenSquareBracket = False
|
||||
$0.TextStylePolicy = $3
|
||||
$3.FileWidth = 80
|
||||
$3.TabsToSpaces = True
|
||||
$3.scope = text/plain
|
||||
EndGlobalSection
|
||||
EndGlobal
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -2,22 +2,22 @@
|
||||
|
||||
<html lang="en" xmlns="http://www.w3.org/1999/xhtml">
|
||||
<head>
|
||||
<meta http-equiv="X-UA-Compatible" content="IE=edge"/>
|
||||
<meta name="viewport" content="user-scalable=no, width=device-width, initial-scale=1.0"/>
|
||||
<meta name="mobile-web-app-capable" content="yes"/>
|
||||
<meta name="apple-mobile-web-app-capable" content="yes"/>
|
||||
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
|
||||
<meta name="viewport" content="user-scalable=no, width=device-width, initial-scale=1.0" />
|
||||
<meta name="mobile-web-app-capable" content="yes" />
|
||||
<meta name="apple-mobile-web-app-capable" content="yes" />
|
||||
<meta charset="utf-8" />
|
||||
<link rel="apple-touch-icon" href="../apple-touch-icon.png"/>
|
||||
<link rel="apple-touch-icon" sizes="57x57" href="../apple-touch-icon-57x57.png"/>
|
||||
<link rel="apple-touch-icon" sizes="72x72" href="../apple-touch-icon-72x72.png"/>
|
||||
<link rel="apple-touch-icon" sizes="76x76" href="../apple-touch-icon-76x76.png"/>
|
||||
<link rel="apple-touch-icon" sizes="114x114" href="../apple-touch-icon-114x114.png"/>
|
||||
<link rel="apple-touch-icon" sizes="120x120" href="../apple-touch-icon-120x120.png"/>
|
||||
<link rel="apple-touch-icon" sizes="144x144" href="../apple-touch-icon-144x144"/>
|
||||
<link rel="apple-touch-icon" sizes="152x152" href="../apple-touch-icon-152x152.png"/>
|
||||
<link rel="apple-touch-icon" sizes="180x180" href="../apple-touch-icon-180x180.png"/>
|
||||
<link rel="apple-touch-icon" href="../apple-touch-icon.png" />
|
||||
<link rel="apple-touch-icon" sizes="57x57" href="../apple-touch-icon-57x57.png" />
|
||||
<link rel="apple-touch-icon" sizes="72x72" href="../apple-touch-icon-72x72.png" />
|
||||
<link rel="apple-touch-icon" sizes="76x76" href="../apple-touch-icon-76x76.png" />
|
||||
<link rel="apple-touch-icon" sizes="114x114" href="../apple-touch-icon-114x114.png" />
|
||||
<link rel="apple-touch-icon" sizes="120x120" href="../apple-touch-icon-120x120.png" />
|
||||
<link rel="apple-touch-icon" sizes="144x144" href="../apple-touch-icon-144x144" />
|
||||
<link rel="apple-touch-icon" sizes="152x152" href="../apple-touch-icon-152x152.png" />
|
||||
<link rel="apple-touch-icon" sizes="180x180" href="../apple-touch-icon-180x180.png" />
|
||||
<link rel="mask-icon" href="jackett_medium.png" color="#35c5f4">
|
||||
<link rel="icon" type="image/ico" href="../favicon.ico"/>
|
||||
<link rel="icon" type="image/ico" href="../favicon.ico" />
|
||||
<link rel='shortcut icon' type='image/x-icon' href='../favicon.ico' />
|
||||
<script src="../libs/filesize.min.js"></script>
|
||||
<script src="../libs/jquery.min.js"></script>
|
||||
@@ -55,13 +55,13 @@
|
||||
<span class="glyphicon glyphicon-plus" aria-hidden="true"></span> Add indexer
|
||||
</button>
|
||||
<button id="jackett-show-search" class="btn btn-success btn-sm">
|
||||
<span class="glyphicon glyphicon-search" aria-hidden="true"></span> Manual Search
|
||||
<span class="glyphicon glyphicon-search" aria-hidden="true"></span> Manual Search
|
||||
</button>
|
||||
<button id="jackett-show-releases" class="btn btn-primary btn-sm">
|
||||
<i class="fa fa-database"></i> View cached releases
|
||||
</button>
|
||||
<button id="jackett-test-all" class="btn btn-warning btn-sm">
|
||||
<span class="glyphicon glyphicon-screenshot" aria-hidden="true"></span> Test All
|
||||
<span class="glyphicon glyphicon-screenshot" aria-hidden="true"></span> Test All
|
||||
</button>
|
||||
</div>
|
||||
<h3>Configured Indexers</h3>
|
||||
@@ -140,6 +140,10 @@
|
||||
<span class="input-header">Enhanced logging: </span>
|
||||
<input id="jackett-logging" class="form-control input-right" type="checkbox" />
|
||||
</div>
|
||||
<div class="input-area">
|
||||
<span class="input-header">OMDB API key: </span>
|
||||
<input id="jackett-omdbkey" class="form-control input-right" type="text" value="" placeholder="">
|
||||
</div>
|
||||
<hr />
|
||||
<div id="footer">
|
||||
Jackett Version <span id="app-version"></span>
|
||||
@@ -183,11 +187,11 @@
|
||||
<div class="setup-item-inputselect">
|
||||
<select class="form-control" data-id="{{id}}">
|
||||
{{#each options}}
|
||||
{{#ifCond ../value @key}}
|
||||
<option value="{{@key}}" selected>{{this}}</option>
|
||||
{{else}}
|
||||
<option value="{{@key}}">{{this}}</option>
|
||||
{{/ifCond}}
|
||||
{{#ifCond ../value @key}}
|
||||
<option value="{{@key}}" selected>{{this}}</option>
|
||||
{{else}}
|
||||
<option value="{{@key}}">{{this}}</option>
|
||||
{{/ifCond}}
|
||||
{{/each}}
|
||||
</select>
|
||||
</div>
|
||||
@@ -212,7 +216,7 @@
|
||||
This indexer has multiple known URLs which you can change above:
|
||||
<ul>
|
||||
{{#each alternativesitelinks}}
|
||||
<li>{{this}}</li>
|
||||
<li>{{this}}</li>
|
||||
{{/each}}
|
||||
</ul>
|
||||
</div>
|
||||
@@ -287,9 +291,14 @@
|
||||
<td class="fit">{{language}}</td>
|
||||
<td class="fit">
|
||||
<div class="indexer-buttons">
|
||||
<button title="Configure" class="btn btn-success btn-xs indexer-setup" data-id="{{id}}" data-link="{{site_link}}">
|
||||
<button title="Configure" class="btn btn-primary btn-xs indexer-setup" data-id="{{id}}" data-link="{{site_link}}">
|
||||
<span class="glyphicon glyphicon-wrench" aria-hidden="true"></span>
|
||||
</button>
|
||||
{{#if_eq type "public"}}
|
||||
<button title="Add" class="btn btn-success btn-xs indexer-add" data-id="{{id}}" data-link="{{site_link}}">
|
||||
<span class="glyphicon glyphicon-plus" aria-hidden="true"></span>
|
||||
</button>
|
||||
{{/if_eq}}
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
@@ -408,7 +417,7 @@
|
||||
<div class="modal-body">
|
||||
<p>You can search all configured indexers from this screen.</p>
|
||||
<label for="text">Query</label>
|
||||
<input type="text" name="query" id="searchquery"/>
|
||||
<input type="text" name="query" id="searchquery" />
|
||||
<label for="tracker">Tracker</label>
|
||||
<select name="tracker" id="searchTracker">
|
||||
<option value="">-- All --</option>
|
||||
@@ -563,7 +572,7 @@
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<button type="button" class="close" data-dismiss="modal" aria-label="Close"><span aria-hidden="true">×</span></button>
|
||||
<h4 class="modal-title">{{title}} - <a target="_blank" href="{{link}}">{{link}}</a></h4>
|
||||
<h4 class="modal-title">{{title}} - <a target="_blank" href="{{link}}">{{link}}</a></h4>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<form class="config-setup-form"></form>
|
||||
@@ -604,6 +613,7 @@
|
||||
<span class="fa fa-search"></span>
|
||||
</script>
|
||||
|
||||
<script src="../libs/api.js"></script>
|
||||
<script src="../custom.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
89
src/Jackett/Content/libs/api.js
Normal file
89
src/Jackett/Content/libs/api.js
Normal file
@@ -0,0 +1,89 @@
|
||||
var api = {
|
||||
version: "2.0",
|
||||
root: "/api",
|
||||
key: "",
|
||||
|
||||
getApiPath: function(category, action) {
|
||||
var path = this.root + "/v" + this.version + "/" + category;
|
||||
if (action !== undefined)
|
||||
path = path + "/" + action
|
||||
return path;
|
||||
},
|
||||
|
||||
getAllIndexers: function(callback) {
|
||||
return $.get(this.getApiPath("indexers"), callback);
|
||||
},
|
||||
|
||||
getServerConfig: function(callback) {
|
||||
return $.get(this.getApiPath("server", "config"), callback);
|
||||
},
|
||||
|
||||
getIndexerConfig: function(indexerId, callback) {
|
||||
return $.get(this.getApiPath("indexers", indexerId + "/config"), callback);
|
||||
},
|
||||
|
||||
updateIndexerConfig: function(indexerId, config, callback) {
|
||||
return $.ajax({
|
||||
url: this.getApiPath("indexers", indexerId + "/config"),
|
||||
type: 'POST',
|
||||
data: JSON.stringify(config),
|
||||
dataType: 'json',
|
||||
contentType: 'application/json',
|
||||
cache: false,
|
||||
success: callback
|
||||
});
|
||||
},
|
||||
|
||||
deleteIndexer: function(indexerId, callback) {
|
||||
return $.ajax({
|
||||
url: this.getApiPath("indexers", indexerId),
|
||||
type: 'DELETE',
|
||||
cache: false,
|
||||
success: callback
|
||||
});
|
||||
},
|
||||
|
||||
testIndexer: function(indexerId, callback) {
|
||||
return $.post(this.getApiPath("indexers", indexerId + "/test"), callback);
|
||||
},
|
||||
|
||||
resultsForIndexer: function(indexerId, query, callback) {
|
||||
return $.get(this.getApiPath("indexers", indexerId + "/results?apikey=" + this.key), query, callback);
|
||||
},
|
||||
|
||||
getServerCache: function(callback) {
|
||||
return $.get(this.getApiPath("indexers", "cache"), callback);
|
||||
},
|
||||
|
||||
getServerLogs: function(callback) {
|
||||
return $.get(this.getApiPath("server", "logs"), callback);
|
||||
},
|
||||
|
||||
updateServerConfig: function(serverConfig, callback) {
|
||||
return $.ajax({
|
||||
url: this.getApiPath("server", "config"),
|
||||
type: 'POST',
|
||||
data: JSON.stringify(serverConfig),
|
||||
dataType: 'json',
|
||||
contentType: 'application/json',
|
||||
cache: false,
|
||||
success: callback
|
||||
});
|
||||
},
|
||||
|
||||
updateServer: function(callback) {
|
||||
return $.post(this.getApiPath("server", "update"), callback);
|
||||
},
|
||||
|
||||
updateAdminPassword: function(password, callback) {
|
||||
return $.ajax({
|
||||
url: this.getApiPath("server", "adminpassword"),
|
||||
type: 'POST',
|
||||
data: JSON.stringify(password),
|
||||
dataType: 'json',
|
||||
contentType: 'application/json',
|
||||
cache: false,
|
||||
success: callback
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -1,120 +0,0 @@
|
||||
<!DOCTYPE html>
|
||||
|
||||
<html lang="en" xmlns="http://www.w3.org/1999/xhtml">
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<script src="jquery-2.1.3.min.js"></script>
|
||||
<script src="common.js"></script>
|
||||
<style>
|
||||
#formItemTemplateContainer {
|
||||
display: none;
|
||||
}
|
||||
</style>
|
||||
<title></title>
|
||||
</head>
|
||||
<body>
|
||||
<script>
|
||||
|
||||
$(function () {
|
||||
|
||||
var urlParams = getUrlParams();
|
||||
|
||||
var jqxhr = $.post("get_config_form", JSON.stringify({ indexer: urlParams.indexer }), function (data) {
|
||||
populateForm(data.config);
|
||||
})
|
||||
.fail(function () {
|
||||
alert("error");
|
||||
});
|
||||
|
||||
$("#loginButton").click(function () {
|
||||
var data = { indexer: urlParams.indexer, config: {} };
|
||||
$("#formItems").children().each(function (i, item) {
|
||||
var $item = $(item);
|
||||
var type = $item.data("type");
|
||||
var id = $item.data("id");
|
||||
var $valEl = $item.find(".formItemValue").children().first();
|
||||
switch (type) {
|
||||
case "inputstring":
|
||||
data.config[id] = $valEl.val();
|
||||
break;
|
||||
case "inputbool":
|
||||
data.config[id] = $valEl.val();
|
||||
break;
|
||||
case "inputselect":
|
||||
data.config[id] = $valEl.val();
|
||||
break;
|
||||
}
|
||||
});
|
||||
|
||||
var jqxhr = $.post("configure_indexer", JSON.stringify(data), function (data) {
|
||||
if (data.result == "error") {
|
||||
if (data.config) {
|
||||
populateForm(data.config);
|
||||
}
|
||||
alert(data.error);
|
||||
|
||||
}
|
||||
})
|
||||
.fail(function () {
|
||||
alert("error");
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
function populateForm(data) {
|
||||
$("#formItems").empty();
|
||||
for (var i = 0; i < data.length; i++) {
|
||||
$("#formItems").append(createFormItem(data[i]));
|
||||
}
|
||||
}
|
||||
|
||||
function createFormItem(itemData) {
|
||||
var $template = $("#formItemTemplate").clone();
|
||||
$template.attr("id", "item" + itemData.id);
|
||||
$template.data("id", itemData.id);
|
||||
$template.data("type", itemData.type);
|
||||
$template.attr("data-type", itemData.type);
|
||||
$template.data("value", itemData.value);
|
||||
$template.find(".formItemName").text(itemData.name);
|
||||
|
||||
$valueElement = $template.find(".formItemValue");
|
||||
|
||||
switch (itemData.type) {
|
||||
case "inputstring":
|
||||
$valueElement.append($("<input type='text'></input>").val(itemData.value));
|
||||
break;
|
||||
case "inputbool":
|
||||
$valueElement.append($("<input type='checkbox'></input>").prop("checked", itemData.value));
|
||||
break;
|
||||
case "displayimage":
|
||||
$valueElement.append($("<img src='" + itemData.value + "'>"));
|
||||
break;
|
||||
case "displayinfo":
|
||||
$valueElement.append($("<span></span>").text(itemData.value));
|
||||
break;
|
||||
}
|
||||
|
||||
return $template;
|
||||
}
|
||||
|
||||
|
||||
|
||||
</script>
|
||||
|
||||
<div id="formItems">
|
||||
|
||||
</div>
|
||||
|
||||
<button id="loginButton">Login</button>
|
||||
|
||||
<div id="formItemTemplateContainer">
|
||||
<div id="formItemTemplate">
|
||||
<div class="formItemName"></div>
|
||||
<div class="formItemValue"></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</body>
|
||||
</html>
|
||||
@@ -1,595 +0,0 @@
|
||||
using Autofac;
|
||||
using AutoMapper;
|
||||
using Jackett.Indexers;
|
||||
using Jackett.Models;
|
||||
using Jackett.Services;
|
||||
using Jackett.Utils;
|
||||
using Newtonsoft.Json;
|
||||
using Newtonsoft.Json.Linq;
|
||||
using NLog;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Net;
|
||||
using System.Net.Http;
|
||||
using System.Net.Http.Headers;
|
||||
using System.Security.Claims;
|
||||
using System.Text;
|
||||
using System.Text.RegularExpressions;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using System.Web;
|
||||
using System.Web.Http;
|
||||
using System.Web.Http.Results;
|
||||
using System.Web.Security;
|
||||
using System.Windows.Forms;
|
||||
|
||||
namespace Jackett.Controllers
|
||||
{
|
||||
[RoutePrefix("admin")]
|
||||
[JackettAuthorized]
|
||||
[JackettAPINoCache]
|
||||
public class AdminController : ApiController
|
||||
{
|
||||
private IConfigurationService config;
|
||||
private IIndexerManagerService indexerService;
|
||||
private IServerService serverService;
|
||||
private ISecuityService securityService;
|
||||
private IProcessService processService;
|
||||
private ICacheService cacheService;
|
||||
private Logger logger;
|
||||
private ILogCacheService logCache;
|
||||
private IUpdateService updater;
|
||||
|
||||
public AdminController(IConfigurationService config, IIndexerManagerService i, IServerService ss, ISecuityService s, IProcessService p, ICacheService c, Logger l, ILogCacheService lc, IUpdateService u)
|
||||
{
|
||||
this.config = config;
|
||||
indexerService = i;
|
||||
serverService = ss;
|
||||
securityService = s;
|
||||
processService = p;
|
||||
cacheService = c;
|
||||
logger = l;
|
||||
logCache = lc;
|
||||
updater = u;
|
||||
}
|
||||
|
||||
private async Task<JToken> ReadPostDataJson()
|
||||
{
|
||||
var content = await Request.Content.ReadAsStringAsync();
|
||||
return JObject.Parse(content);
|
||||
}
|
||||
|
||||
|
||||
private HttpResponseMessage GetFile(string path)
|
||||
{
|
||||
var result = new HttpResponseMessage(HttpStatusCode.OK);
|
||||
var mappedPath = Path.Combine(config.GetContentFolder(), path);
|
||||
var stream = new FileStream(mappedPath, FileMode.Open, FileAccess.Read, FileShare.Read);
|
||||
result.Content = new StreamContent(stream);
|
||||
result.Content.Headers.ContentType = new MediaTypeHeaderValue(MimeMapping.GetMimeMapping(mappedPath));
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
[HttpGet]
|
||||
[AllowAnonymous]
|
||||
public RedirectResult Logout()
|
||||
{
|
||||
var ctx = Request.GetOwinContext();
|
||||
var authManager = ctx.Authentication;
|
||||
authManager.SignOut("ApplicationCookie");
|
||||
return Redirect("Admin/Dashboard");
|
||||
}
|
||||
|
||||
[HttpGet]
|
||||
[HttpPost]
|
||||
[AllowAnonymous]
|
||||
public async Task<HttpResponseMessage> Dashboard()
|
||||
{
|
||||
if (Request.RequestUri.Query != null && Request.RequestUri.Query.Contains("logout"))
|
||||
{
|
||||
var file = GetFile("login.html");
|
||||
securityService.Logout(file);
|
||||
return file;
|
||||
}
|
||||
|
||||
|
||||
if (securityService.CheckAuthorised(Request))
|
||||
{
|
||||
return GetFile("index.html");
|
||||
|
||||
}
|
||||
else
|
||||
{
|
||||
var formData = await Request.Content.ReadAsFormDataAsync();
|
||||
|
||||
if (formData != null && securityService.HashPassword(formData["password"]) == serverService.Config.AdminPassword)
|
||||
{
|
||||
var file = GetFile("index.html");
|
||||
securityService.Login(file);
|
||||
return file;
|
||||
}
|
||||
else
|
||||
{
|
||||
return GetFile("login.html");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
[Route("set_admin_password")]
|
||||
[HttpPost]
|
||||
public async Task<IHttpActionResult> SetAdminPassword()
|
||||
{
|
||||
var jsonReply = new JObject();
|
||||
try
|
||||
{
|
||||
var postData = await ReadPostDataJson();
|
||||
var password = (string)postData["password"];
|
||||
if (string.IsNullOrEmpty(password))
|
||||
{
|
||||
serverService.Config.AdminPassword = string.Empty;
|
||||
}
|
||||
else
|
||||
{
|
||||
serverService.Config.AdminPassword = securityService.HashPassword(password);
|
||||
}
|
||||
|
||||
serverService.SaveConfig();
|
||||
jsonReply["result"] = "success";
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
logger.Error(ex, "Exception in SetAdminPassword");
|
||||
jsonReply["result"] = "error";
|
||||
jsonReply["error"] = ex.Message;
|
||||
}
|
||||
return Json(jsonReply);
|
||||
}
|
||||
|
||||
[Route("get_config_form")]
|
||||
[HttpPost]
|
||||
public async Task<IHttpActionResult> GetConfigForm()
|
||||
{
|
||||
var jsonReply = new JObject();
|
||||
try
|
||||
{
|
||||
var postData = await ReadPostDataJson();
|
||||
var indexer = indexerService.GetIndexer((string)postData["indexer"]);
|
||||
var config = await indexer.GetConfigurationForSetup();
|
||||
jsonReply["config"] = config.ToJson(null);
|
||||
jsonReply["caps"] = indexer.TorznabCaps.CapsToJson();
|
||||
jsonReply["name"] = indexer.DisplayName;
|
||||
jsonReply["alternativesitelinks"] = JToken.FromObject(indexer.AlternativeSiteLinks);
|
||||
jsonReply["result"] = "success";
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
logger.Error(ex, "Exception in GetConfigForm");
|
||||
jsonReply["result"] = "error";
|
||||
jsonReply["error"] = ex.Message;
|
||||
}
|
||||
return Json(jsonReply);
|
||||
}
|
||||
|
||||
[Route("configure_indexer")]
|
||||
[HttpPost]
|
||||
public async Task<IHttpActionResult> Configure()
|
||||
{
|
||||
var jsonReply = new JObject();
|
||||
IIndexer indexer = null;
|
||||
try
|
||||
{
|
||||
var postData = await ReadPostDataJson();
|
||||
string indexerString = (string)postData["indexer"];
|
||||
indexer = indexerService.GetIndexer((string)postData["indexer"]);
|
||||
jsonReply["name"] = indexer.DisplayName;
|
||||
var configurationResult = await indexer.ApplyConfiguration(postData["config"]);
|
||||
if (configurationResult == IndexerConfigurationStatus.RequiresTesting)
|
||||
{
|
||||
await indexerService.TestIndexer((string)postData["indexer"]);
|
||||
}
|
||||
else if (configurationResult == IndexerConfigurationStatus.Failed)
|
||||
{
|
||||
throw new Exception("Configuration Failed");
|
||||
}
|
||||
jsonReply["result"] = "success";
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
jsonReply["result"] = "error";
|
||||
jsonReply["error"] = ex.Message;
|
||||
var baseIndexer = indexer as BaseIndexer;
|
||||
if (null != baseIndexer)
|
||||
baseIndexer.ResetBaseConfig();
|
||||
if (ex is ExceptionWithConfigData)
|
||||
{
|
||||
jsonReply["config"] = ((ExceptionWithConfigData)ex).ConfigData.ToJson(null,false);
|
||||
}
|
||||
else
|
||||
{
|
||||
logger.Error(ex, "Exception in Configure");
|
||||
}
|
||||
}
|
||||
return Json(jsonReply);
|
||||
}
|
||||
|
||||
[Route("get_indexers")]
|
||||
[HttpGet]
|
||||
public IHttpActionResult Indexers()
|
||||
{
|
||||
var jsonReply = new JObject();
|
||||
try
|
||||
{
|
||||
jsonReply["result"] = "success";
|
||||
JArray items = new JArray();
|
||||
|
||||
foreach (var indexer in indexerService.GetAllIndexers())
|
||||
{
|
||||
var item = new JObject();
|
||||
item["id"] = indexer.ID;
|
||||
item["name"] = indexer.DisplayName;
|
||||
item["description"] = indexer.DisplayDescription;
|
||||
item["type"] = indexer.Type;
|
||||
item["configured"] = indexer.IsConfigured;
|
||||
item["site_link"] = indexer.SiteLink;
|
||||
item["language"] = indexer.Language;
|
||||
item["last_error"] = indexer.LastError;
|
||||
item["potatoenabled"] = indexer.TorznabCaps.Categories.Select(c => c.ID).Any(i => PotatoController.MOVIE_CATS.Contains(i));
|
||||
|
||||
var caps = new JObject();
|
||||
foreach (var cap in indexer.TorznabCaps.Categories)
|
||||
caps[cap.ID.ToString()] = cap.Name;
|
||||
item["caps"] = caps;
|
||||
items.Add(item);
|
||||
}
|
||||
jsonReply["items"] = items;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
logger.Error(ex, "Exception in get_indexers");
|
||||
jsonReply["result"] = "error";
|
||||
jsonReply["error"] = ex.Message;
|
||||
}
|
||||
return Json(jsonReply);
|
||||
}
|
||||
|
||||
[Route("test_indexer")]
|
||||
[HttpPost]
|
||||
public async Task<IHttpActionResult> Test()
|
||||
{
|
||||
JToken jsonReply = new JObject();
|
||||
IIndexer indexer = null;
|
||||
try
|
||||
{
|
||||
var postData = await ReadPostDataJson();
|
||||
string indexerString = (string)postData["indexer"];
|
||||
indexer = indexerService.GetIndexer(indexerString);
|
||||
await indexerService.TestIndexer(indexerString);
|
||||
jsonReply["name"] = indexer.DisplayName;
|
||||
jsonReply["result"] = "success";
|
||||
indexer.LastError = null;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
var msg = ex.Message;
|
||||
if (ex.InnerException != null)
|
||||
msg += ": " + ex.InnerException.Message;
|
||||
logger.Error(ex, "Exception in test_indexer");
|
||||
jsonReply["result"] = "error";
|
||||
jsonReply["error"] = msg;
|
||||
if (indexer != null)
|
||||
indexer.LastError = msg;
|
||||
}
|
||||
return Json(jsonReply);
|
||||
}
|
||||
|
||||
[Route("delete_indexer")]
|
||||
[HttpPost]
|
||||
public async Task<IHttpActionResult> Delete()
|
||||
{
|
||||
var jsonReply = new JObject();
|
||||
try
|
||||
{
|
||||
var postData = await ReadPostDataJson();
|
||||
string indexerString = (string)postData["indexer"];
|
||||
indexerService.DeleteIndexer(indexerString);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
logger.Error(ex, "Exception in delete_indexer");
|
||||
jsonReply["result"] = "error";
|
||||
jsonReply["error"] = ex.Message;
|
||||
}
|
||||
return Json(jsonReply);
|
||||
}
|
||||
|
||||
[Route("trigger_update")]
|
||||
[HttpGet]
|
||||
public IHttpActionResult TriggerUpdates()
|
||||
{
|
||||
var jsonReply = new JObject();
|
||||
updater.CheckForUpdatesNow();
|
||||
return Json(jsonReply);
|
||||
}
|
||||
|
||||
[Route("get_jackett_config")]
|
||||
[HttpGet]
|
||||
public IHttpActionResult GetConfig()
|
||||
{
|
||||
var jsonReply = new JObject();
|
||||
try
|
||||
{
|
||||
var cfg = new JObject();
|
||||
cfg["notices"] = JToken.FromObject(serverService.notices);
|
||||
cfg["port"] = serverService.Config.Port;
|
||||
cfg["external"] = serverService.Config.AllowExternal;
|
||||
cfg["api_key"] = serverService.Config.APIKey;
|
||||
cfg["blackholedir"] = serverService.Config.BlackholeDir;
|
||||
cfg["updatedisabled"] = serverService.Config.UpdateDisabled;
|
||||
cfg["prerelease"] = serverService.Config.UpdatePrerelease;
|
||||
cfg["password"] = string.IsNullOrEmpty(serverService.Config.AdminPassword) ? string.Empty : serverService.Config.AdminPassword.Substring(0, 10);
|
||||
cfg["logging"] = Startup.TracingEnabled;
|
||||
cfg["basepathoverride"] = serverService.Config.BasePathOverride;
|
||||
|
||||
|
||||
jsonReply["config"] = cfg;
|
||||
jsonReply["app_version"] = config.GetVersion();
|
||||
jsonReply["result"] = "success";
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
logger.Error(ex, "Exception in get_jackett_config");
|
||||
jsonReply["result"] = "error";
|
||||
jsonReply["error"] = ex.Message;
|
||||
}
|
||||
return Json(jsonReply);
|
||||
}
|
||||
|
||||
[Route("set_config")]
|
||||
[HttpPost]
|
||||
public async Task<IHttpActionResult> SetConfig()
|
||||
{
|
||||
var originalPort = Engine.Server.Config.Port;
|
||||
var originalAllowExternal = Engine.Server.Config.AllowExternal;
|
||||
var jsonReply = new JObject();
|
||||
try
|
||||
{
|
||||
var postData = await ReadPostDataJson();
|
||||
int port = (int)postData["port"];
|
||||
bool external = (bool)postData["external"];
|
||||
string saveDir = (string)postData["blackholedir"];
|
||||
bool updateDisabled = (bool)postData["updatedisabled"];
|
||||
bool preRelease = (bool)postData["prerelease"];
|
||||
bool logging = (bool)postData["logging"];
|
||||
string basePathOverride = (string)postData["basepathoverride"];
|
||||
|
||||
Engine.Server.Config.UpdateDisabled = updateDisabled;
|
||||
Engine.Server.Config.UpdatePrerelease = preRelease;
|
||||
Engine.Server.Config.BasePathOverride = basePathOverride;
|
||||
Startup.BasePath = Engine.Server.BasePath();
|
||||
Engine.Server.SaveConfig();
|
||||
|
||||
Engine.SetLogLevel(logging ? LogLevel.Debug : LogLevel.Info);
|
||||
Startup.TracingEnabled = logging;
|
||||
|
||||
if (port != Engine.Server.Config.Port || external != Engine.Server.Config.AllowExternal)
|
||||
{
|
||||
|
||||
if (ServerUtil.RestrictedPorts.Contains(port))
|
||||
{
|
||||
jsonReply["result"] = "error";
|
||||
jsonReply["error"] = "The port you have selected is restricted, try a different one.";
|
||||
return Json(jsonReply);
|
||||
}
|
||||
|
||||
// Save port to the config so it can be picked up by the if needed when running as admin below.
|
||||
Engine.Server.Config.AllowExternal = external;
|
||||
Engine.Server.Config.Port = port;
|
||||
Engine.Server.SaveConfig();
|
||||
|
||||
// On Windows change the url reservations
|
||||
if (System.Environment.OSVersion.Platform != PlatformID.Unix)
|
||||
{
|
||||
if (!ServerUtil.IsUserAdministrator())
|
||||
{
|
||||
try
|
||||
{
|
||||
processService.StartProcessAndLog(Application.ExecutablePath, "--ReserveUrls", true);
|
||||
}
|
||||
catch
|
||||
{
|
||||
Engine.Server.Config.Port = originalPort;
|
||||
Engine.Server.Config.AllowExternal = originalAllowExternal;
|
||||
Engine.Server.SaveConfig();
|
||||
jsonReply["result"] = "error";
|
||||
jsonReply["error"] = "Failed to acquire admin permissions to reserve the new port.";
|
||||
return Json(jsonReply);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
serverService.ReserveUrls(true);
|
||||
}
|
||||
}
|
||||
|
||||
(new Thread(() =>
|
||||
{
|
||||
Thread.Sleep(500);
|
||||
serverService.Stop();
|
||||
Engine.BuildContainer();
|
||||
Engine.Server.Initalize();
|
||||
Engine.Server.Start();
|
||||
})).Start();
|
||||
}
|
||||
|
||||
if (saveDir != Engine.Server.Config.BlackholeDir)
|
||||
{
|
||||
if (!string.IsNullOrEmpty(saveDir))
|
||||
{
|
||||
if (!Directory.Exists(saveDir))
|
||||
{
|
||||
throw new Exception("Blackhole directory does not exist");
|
||||
}
|
||||
}
|
||||
|
||||
Engine.Server.Config.BlackholeDir = saveDir;
|
||||
Engine.Server.SaveConfig();
|
||||
}
|
||||
|
||||
jsonReply["result"] = "success";
|
||||
jsonReply["port"] = port;
|
||||
jsonReply["external"] = external;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
logger.Error(ex, "Exception in set_port");
|
||||
jsonReply["result"] = "error";
|
||||
jsonReply["error"] = ex.Message;
|
||||
}
|
||||
return Json(jsonReply);
|
||||
}
|
||||
|
||||
[Route("GetCache")]
|
||||
[HttpGet]
|
||||
public List<TrackerCacheResult> GetCache()
|
||||
{
|
||||
var results = cacheService.GetCachedResults();
|
||||
ConfigureCacheResults(results);
|
||||
return results;
|
||||
}
|
||||
|
||||
|
||||
private void ConfigureCacheResults(List<TrackerCacheResult> results)
|
||||
{
|
||||
var serverUrl = string.Format("{0}://{1}:{2}{3}", Request.RequestUri.Scheme, Request.RequestUri.Host, Request.RequestUri.Port, serverService.BasePath());
|
||||
foreach (var result in results)
|
||||
{
|
||||
var link = result.Link;
|
||||
result.Link = serverService.ConvertToProxyLink(link, serverUrl, result.TrackerId, "dl", result.Title + ".torrent");
|
||||
if (result.Link != null && result.Link.Scheme != "magnet" && !string.IsNullOrWhiteSpace(Engine.Server.Config.BlackholeDir))
|
||||
result.BlackholeLink = serverService.ConvertToProxyLink(link, serverUrl, result.TrackerId, "bh", string.Empty);
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
[Route("GetLogs")]
|
||||
[HttpGet]
|
||||
public List<CachedLog> GetLogs()
|
||||
{
|
||||
return logCache.Logs;
|
||||
}
|
||||
|
||||
[Route("Search")]
|
||||
[HttpPost]
|
||||
public ManualSearchResult Search([FromBody]AdminSearch value)
|
||||
{
|
||||
var results = new List<TrackerCacheResult>();
|
||||
var stringQuery = new TorznabQuery();
|
||||
|
||||
var queryStr = value.Query;
|
||||
if (queryStr != null)
|
||||
{
|
||||
var seasonMatch = Regex.Match(queryStr, @"S(\d{2,4})");
|
||||
if (seasonMatch.Success)
|
||||
{
|
||||
stringQuery.Season = int.Parse(seasonMatch.Groups[1].Value);
|
||||
queryStr = queryStr.Remove(seasonMatch.Index, seasonMatch.Length);
|
||||
}
|
||||
|
||||
var episodeMatch = Regex.Match(queryStr, @"E(\d{2,4})");
|
||||
if (episodeMatch.Success)
|
||||
{
|
||||
stringQuery.Episode = episodeMatch.Groups[1].Value;
|
||||
queryStr = queryStr.Remove(episodeMatch.Index, episodeMatch.Length);
|
||||
}
|
||||
queryStr = queryStr.Trim();
|
||||
}
|
||||
|
||||
|
||||
stringQuery.SearchTerm = queryStr;
|
||||
stringQuery.Categories = value.Category == 0 ? new int[0] : new int[1] { value.Category };
|
||||
stringQuery.ExpandCatsToSubCats();
|
||||
|
||||
// try to build an IMDB Query
|
||||
var imdbID = ParseUtil.GetFullImdbID(stringQuery.SanitizedSearchTerm);
|
||||
TorznabQuery imdbQuery = null;
|
||||
if (imdbID != null)
|
||||
{
|
||||
imdbQuery = new TorznabQuery()
|
||||
{
|
||||
ImdbID = imdbID,
|
||||
Categories = stringQuery.Categories,
|
||||
Season = stringQuery.Season,
|
||||
Episode = stringQuery.Episode,
|
||||
};
|
||||
imdbQuery.ExpandCatsToSubCats();
|
||||
}
|
||||
|
||||
var trackers = indexerService.GetAllIndexers().Where(t => t.IsConfigured).ToList();
|
||||
if (!string.IsNullOrWhiteSpace(value.Tracker))
|
||||
{
|
||||
trackers = trackers.Where(t => t.ID == value.Tracker).ToList();
|
||||
}
|
||||
|
||||
if (value.Category != 0)
|
||||
{
|
||||
trackers = trackers.Where(t => t.TorznabCaps.Categories.Select(c => c.ID).Contains(value.Category)).ToList();
|
||||
}
|
||||
|
||||
Parallel.ForEach(trackers.ToList(), new ParallelOptions { MaxDegreeOfParallelism = 1000 }, indexer =>
|
||||
{
|
||||
try
|
||||
{
|
||||
var query = stringQuery;
|
||||
// use imdb Query for trackers which support it
|
||||
if (imdbQuery != null && indexer.TorznabCaps.SupportsImdbSearch)
|
||||
query = imdbQuery;
|
||||
|
||||
var searchResults = indexer.PerformQuery(query).Result;
|
||||
searchResults = indexer.CleanLinks(searchResults);
|
||||
cacheService.CacheRssResults(indexer, searchResults);
|
||||
searchResults = indexer.FilterResults(query, searchResults);
|
||||
|
||||
foreach (var result in searchResults)
|
||||
{
|
||||
var item = Mapper.Map<TrackerCacheResult>(result);
|
||||
item.Tracker = indexer.DisplayName;
|
||||
item.TrackerId = indexer.ID;
|
||||
item.Peers = item.Peers - item.Seeders; // Use peers as leechers
|
||||
lock (results)
|
||||
{
|
||||
results.Add(item);
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
logger.Error(e, "An error occured during manual search on " + indexer.DisplayName + ": " + e.Message);
|
||||
}
|
||||
});
|
||||
|
||||
ConfigureCacheResults(results);
|
||||
|
||||
if (trackers.Count > 1)
|
||||
{
|
||||
results = results.OrderByDescending(d => d.PublishDate).ToList();
|
||||
}
|
||||
|
||||
var manualResult = new ManualSearchResult()
|
||||
{
|
||||
Results = results,
|
||||
Indexers = trackers.Select(t => t.DisplayName).ToList()
|
||||
};
|
||||
|
||||
|
||||
if (manualResult.Indexers.Count == 0)
|
||||
manualResult.Indexers = new List<string>() { "None" };
|
||||
|
||||
logger.Info(string.Format("Manual search for \"{0}\" on {1} with {2} results.", stringQuery.GetQueryString(), string.Join(", ", manualResult.Indexers), manualResult.Results.Count));
|
||||
return manualResult;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
using Jackett.Services;
|
||||
using Jackett.Utils;
|
||||
using Newtonsoft.Json.Linq;
|
||||
using NLog;
|
||||
using System;
|
||||
@@ -22,34 +23,36 @@ namespace Jackett.Controllers
|
||||
private Logger logger;
|
||||
private IIndexerManagerService indexerService;
|
||||
IServerService serverService;
|
||||
IProtectionService protectionService;
|
||||
|
||||
public BlackholeController(IIndexerManagerService i, Logger l, IServerService s)
|
||||
public BlackholeController(IIndexerManagerService i, Logger l, IServerService s, IProtectionService ps)
|
||||
{
|
||||
logger = l;
|
||||
indexerService = i;
|
||||
serverService = s;
|
||||
protectionService = ps;
|
||||
}
|
||||
|
||||
[HttpGet]
|
||||
public async Task<IHttpActionResult> Blackhole(string indexerID, string path, string apikey)
|
||||
public async Task<IHttpActionResult> Blackhole(string indexerID, string path, string jackett_apikey, string file)
|
||||
{
|
||||
|
||||
var jsonReply = new JObject();
|
||||
try
|
||||
{
|
||||
var indexer = indexerService.GetIndexer(indexerID);
|
||||
var indexer = indexerService.GetWebIndexer(indexerID);
|
||||
if (!indexer.IsConfigured)
|
||||
{
|
||||
logger.Warn(string.Format("Rejected a request to {0} which is unconfigured.", indexer.DisplayName));
|
||||
throw new Exception("This indexer is not configured.");
|
||||
}
|
||||
|
||||
if (serverService.Config.APIKey != apikey)
|
||||
if (serverService.Config.APIKey != jackett_apikey)
|
||||
throw new Exception("Incorrect API key");
|
||||
|
||||
var remoteFile = new Uri(Encoding.UTF8.GetString(HttpServerUtility.UrlTokenDecode(path)), UriKind.RelativeOrAbsolute);
|
||||
remoteFile = indexer.UncleanLink(remoteFile);
|
||||
|
||||
path = Encoding.UTF8.GetString(HttpServerUtility.UrlTokenDecode(path));
|
||||
path = protectionService.UnProtect(path);
|
||||
var remoteFile = new Uri(path, UriKind.RelativeOrAbsolute);
|
||||
var downloadBytes = await indexer.Download(remoteFile);
|
||||
|
||||
if (string.IsNullOrWhiteSpace(Engine.Server.Config.BlackholeDir))
|
||||
@@ -62,7 +65,12 @@ namespace Jackett.Controllers
|
||||
throw new Exception("Blackhole directory does not exist: " + Engine.Server.Config.BlackholeDir);
|
||||
}
|
||||
|
||||
var fileName = DateTime.Now.Ticks + ".torrent";
|
||||
var fileName = DateTime.Now.Ticks.ToString() + "-" + StringUtil.MakeValidFileName(indexer.DisplayName, '_', false);
|
||||
if (string.IsNullOrWhiteSpace(file))
|
||||
fileName += ".torrent";
|
||||
else
|
||||
fileName += "-"+StringUtil.MakeValidFileName(file, '_', false); // call MakeValidFileName() again to avoid any possibility of path traversal attacks
|
||||
|
||||
File.WriteAllBytes(Path.Combine(Engine.Server.Config.BlackholeDir, fileName), downloadBytes);
|
||||
jsonReply["result"] = "success";
|
||||
}
|
||||
|
||||
@@ -11,6 +11,7 @@ using System.Threading.Tasks;
|
||||
using System.Web;
|
||||
using System.Web.Http;
|
||||
using MonoTorrent.BEncoding;
|
||||
using Jackett.Utils;
|
||||
|
||||
namespace Jackett.Controllers
|
||||
{
|
||||
@@ -21,20 +22,22 @@ namespace Jackett.Controllers
|
||||
Logger logger;
|
||||
IIndexerManagerService indexerService;
|
||||
IServerService serverService;
|
||||
IProtectionService protectionService;
|
||||
|
||||
public DownloadController(IIndexerManagerService i, Logger l, IServerService s)
|
||||
public DownloadController(IIndexerManagerService i, Logger l, IServerService s, IProtectionService ps)
|
||||
{
|
||||
logger = l;
|
||||
indexerService = i;
|
||||
serverService = s;
|
||||
protectionService = ps;
|
||||
}
|
||||
|
||||
[HttpGet]
|
||||
public async Task<HttpResponseMessage> Download(string indexerID, string path, string apikey, string file)
|
||||
public async Task<HttpResponseMessage> Download(string indexerID, string path, string jackett_apikey, string file)
|
||||
{
|
||||
try
|
||||
{
|
||||
var indexer = indexerService.GetIndexer(indexerID);
|
||||
var indexer = indexerService.GetWebIndexer(indexerID);
|
||||
|
||||
if (!indexer.IsConfigured)
|
||||
{
|
||||
@@ -43,31 +46,24 @@ namespace Jackett.Controllers
|
||||
}
|
||||
|
||||
path = Encoding.UTF8.GetString(HttpServerUtility.UrlTokenDecode(path));
|
||||
path = protectionService.UnProtect(path);
|
||||
|
||||
if (serverService.Config.APIKey != apikey)
|
||||
if (serverService.Config.APIKey != jackett_apikey)
|
||||
return new HttpResponseMessage(HttpStatusCode.Unauthorized);
|
||||
|
||||
var target = new Uri(path, UriKind.RelativeOrAbsolute);
|
||||
target = indexer.UncleanLink(target);
|
||||
|
||||
var downloadBytes = await indexer.Download(target);
|
||||
|
||||
// This will fix torrents where the keys are not sorted, and thereby not supported by Sonarr.
|
||||
var torrentDictionary = BEncodedDictionary.DecodeTorrent(downloadBytes);
|
||||
downloadBytes = torrentDictionary.Encode();
|
||||
|
||||
char[] invalidChars = System.IO.Path.GetInvalidFileNameChars();
|
||||
for(int i=0;i<file.Count();i++)
|
||||
if(invalidChars.Contains(file[i])) {
|
||||
file = file.Remove(i, 1).Insert(i, " ");
|
||||
}
|
||||
|
||||
var result = new HttpResponseMessage(HttpStatusCode.OK);
|
||||
result.Content = new ByteArrayContent(downloadBytes);
|
||||
result.Content.Headers.ContentType = new MediaTypeHeaderValue("application/x-bittorrent");
|
||||
result.Content.Headers.ContentDisposition = new ContentDispositionHeaderValue("attachment")
|
||||
{
|
||||
FileName = file
|
||||
FileName = StringUtil.MakeValidFileName(file, '_', false) // call MakeValidFileName again to avoid any kind of injection attack
|
||||
};
|
||||
return result;
|
||||
}
|
||||
|
||||
173
src/Jackett/Controllers/IndexerApiController.cs
Normal file
173
src/Jackett/Controllers/IndexerApiController.cs
Normal file
@@ -0,0 +1,173 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text.RegularExpressions;
|
||||
using System.Threading.Tasks;
|
||||
using System.Web.Http;
|
||||
using System.Web.Http.Controllers;
|
||||
using System.Web.Http.Filters;
|
||||
using AutoMapper;
|
||||
using Jackett.Indexers;
|
||||
using Jackett.Models;
|
||||
using Jackett.Services;
|
||||
using Jackett.Utils;
|
||||
using Newtonsoft.Json;
|
||||
using Newtonsoft.Json.Linq;
|
||||
using NLog;
|
||||
|
||||
namespace Jackett.Controllers.V20
|
||||
{
|
||||
public interface IIndexerController
|
||||
{
|
||||
IIndexerManagerService IndexerService { get; }
|
||||
IIndexer CurrentIndexer { get; set; }
|
||||
}
|
||||
|
||||
public class RequiresIndexerAttribute : ActionFilterAttribute
|
||||
{
|
||||
public override void OnActionExecuting(HttpActionContext actionContext)
|
||||
{
|
||||
base.OnActionExecuting(actionContext);
|
||||
|
||||
var controller = actionContext.ControllerContext.Controller;
|
||||
if (!(controller is IIndexerController))
|
||||
return;
|
||||
|
||||
var indexerController = controller as IIndexerController;
|
||||
|
||||
var parameters = actionContext.RequestContext.RouteData.Values;
|
||||
|
||||
if (!parameters.ContainsKey("indexerId"))
|
||||
{
|
||||
indexerController.CurrentIndexer = null;
|
||||
return;
|
||||
}
|
||||
|
||||
var indexerId = parameters["indexerId"] as string;
|
||||
if (indexerId.IsNullOrEmptyOrWhitespace())
|
||||
return;
|
||||
|
||||
var indexerService = indexerController.IndexerService;
|
||||
var indexer = indexerService.GetIndexer(indexerId);
|
||||
indexerController.CurrentIndexer = indexer;
|
||||
}
|
||||
}
|
||||
|
||||
[RoutePrefix("api/v2.0/indexers")]
|
||||
[JackettAuthorized]
|
||||
[JackettAPINoCache]
|
||||
public class IndexerApiController : ApiController, IIndexerController
|
||||
{
|
||||
public IIndexerManagerService IndexerService { get; private set; }
|
||||
public IIndexer CurrentIndexer { get; set; }
|
||||
|
||||
public IndexerApiController(IIndexerManagerService indexerManagerService, IServerService ss, ICacheService c, Logger logger)
|
||||
{
|
||||
IndexerService = indexerManagerService;
|
||||
serverService = ss;
|
||||
cacheService = c;
|
||||
this.logger = logger;
|
||||
}
|
||||
|
||||
[HttpGet]
|
||||
[RequiresIndexer]
|
||||
public async Task<IHttpActionResult> Config()
|
||||
{
|
||||
var config = await CurrentIndexer.GetConfigurationForSetup();
|
||||
return Ok(config.ToJson(null));
|
||||
}
|
||||
|
||||
[HttpPost]
|
||||
[ActionName("Config")]
|
||||
[RequiresIndexer]
|
||||
public async Task UpdateConfig([FromBody]Models.DTO.ConfigItem[] config)
|
||||
{
|
||||
try
|
||||
{
|
||||
// HACK
|
||||
var jsonString = JsonConvert.SerializeObject(config);
|
||||
var json = JToken.Parse(jsonString);
|
||||
|
||||
var configurationResult = await CurrentIndexer.ApplyConfiguration(json);
|
||||
|
||||
if (configurationResult == IndexerConfigurationStatus.RequiresTesting)
|
||||
await IndexerService.TestIndexer(CurrentIndexer.ID);
|
||||
}
|
||||
catch
|
||||
{
|
||||
var baseIndexer = CurrentIndexer as BaseIndexer;
|
||||
if (null != baseIndexer)
|
||||
baseIndexer.ResetBaseConfig();
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
[HttpGet]
|
||||
[Route("")]
|
||||
public IEnumerable<Models.DTO.Indexer> Indexers()
|
||||
{
|
||||
var dto = IndexerService.GetAllIndexers().Select(i => new Models.DTO.Indexer(i));
|
||||
return dto;
|
||||
}
|
||||
|
||||
[HttpPost]
|
||||
[RequiresIndexer]
|
||||
public async Task Test()
|
||||
{
|
||||
JToken jsonReply = new JObject();
|
||||
try
|
||||
{
|
||||
await IndexerService.TestIndexer(CurrentIndexer.ID);
|
||||
CurrentIndexer.LastError = null;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
var msg = ex.Message;
|
||||
if (ex.InnerException != null)
|
||||
msg += ": " + ex.InnerException.Message;
|
||||
|
||||
if (CurrentIndexer != null)
|
||||
CurrentIndexer.LastError = msg;
|
||||
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
[HttpDelete]
|
||||
[RequiresIndexer]
|
||||
[Route("{indexerId}")]
|
||||
public void Delete()
|
||||
{
|
||||
IndexerService.DeleteIndexer(CurrentIndexer.ID);
|
||||
}
|
||||
|
||||
// TODO
|
||||
// This should go to ServerConfigurationController
|
||||
[Route("Cache")]
|
||||
[HttpGet]
|
||||
public List<TrackerCacheResult> Cache()
|
||||
{
|
||||
var results = cacheService.GetCachedResults();
|
||||
ConfigureCacheResults(results);
|
||||
return results;
|
||||
}
|
||||
|
||||
private void ConfigureCacheResults(IEnumerable<TrackerCacheResult> results)
|
||||
{
|
||||
var serverUrl = string.Format("{0}://{1}:{2}{3}", Request.RequestUri.Scheme, Request.RequestUri.Host, Request.RequestUri.Port, serverService.BasePath());
|
||||
foreach (var result in results)
|
||||
{
|
||||
var link = result.Link;
|
||||
var file = StringUtil.MakeValidFileName(result.Title, '_', false) + ".torrent";
|
||||
result.Link = serverService.ConvertToProxyLink(link, serverUrl, result.TrackerId, "dl", file);
|
||||
if (result.Link != null && result.Link.Scheme != "magnet" && !string.IsNullOrWhiteSpace(Engine.Server.Config.BlackholeDir))
|
||||
result.BlackholeLink = serverService.ConvertToProxyLink(link, serverUrl, result.TrackerId, "bh", file);
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
private Logger logger;
|
||||
private IServerService serverService;
|
||||
private ICacheService cacheService;
|
||||
}
|
||||
}
|
||||
@@ -1,173 +0,0 @@
|
||||
using AutoMapper;
|
||||
using Jackett.Models;
|
||||
using Jackett.Services;
|
||||
using Jackett.Utils;
|
||||
using Jackett.Utils.Clients;
|
||||
using Newtonsoft.Json.Linq;
|
||||
using NLog;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.Linq;
|
||||
using System.Net;
|
||||
using System.Net.Http;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using System.Web;
|
||||
using System.Web.Http;
|
||||
|
||||
namespace Jackett.Controllers
|
||||
{
|
||||
[AllowAnonymous]
|
||||
[JackettAPINoCache]
|
||||
public class PotatoController : ApiController
|
||||
{
|
||||
private IIndexerManagerService indexerService;
|
||||
private Logger logger;
|
||||
private IServerService serverService;
|
||||
private ICacheService cacheService;
|
||||
private IWebClient webClient;
|
||||
|
||||
public static int[] MOVIE_CATS
|
||||
{
|
||||
get
|
||||
{
|
||||
var torznabQuery = new TorznabQuery()
|
||||
{
|
||||
Categories = new int[1] { TorznabCatType.Movies.ID },
|
||||
};
|
||||
|
||||
torznabQuery.ExpandCatsToSubCats();
|
||||
return torznabQuery.Categories;
|
||||
}
|
||||
}
|
||||
|
||||
public PotatoController(IIndexerManagerService i, Logger l, IServerService s, ICacheService c, IWebClient w)
|
||||
{
|
||||
indexerService = i;
|
||||
logger = l;
|
||||
serverService = s;
|
||||
cacheService = c;
|
||||
webClient = w;
|
||||
}
|
||||
|
||||
[HttpGet]
|
||||
public async Task<HttpResponseMessage> Call(string indexerID, [FromUri]TorrentPotatoRequest request)
|
||||
{
|
||||
var indexer = indexerService.GetIndexer(indexerID);
|
||||
|
||||
var allowBadApiDueToDebug = false;
|
||||
#if DEBUG
|
||||
allowBadApiDueToDebug = Debugger.IsAttached;
|
||||
#endif
|
||||
|
||||
if (!allowBadApiDueToDebug && !string.Equals(request.passkey, serverService.Config.APIKey, StringComparison.InvariantCultureIgnoreCase))
|
||||
{
|
||||
logger.Warn(string.Format("A request from {0} was made with an incorrect API key.", Request.GetOwinContext().Request.RemoteIpAddress));
|
||||
return Request.CreateResponse(HttpStatusCode.Forbidden, "Incorrect API key");
|
||||
}
|
||||
|
||||
if (!indexer.IsConfigured)
|
||||
{
|
||||
logger.Warn(string.Format("Rejected a request to {0} which is unconfigured.", indexer.DisplayName));
|
||||
return Request.CreateResponse(HttpStatusCode.Forbidden, "This indexer is not configured.");
|
||||
}
|
||||
|
||||
if (!indexer.TorznabCaps.Categories.Select(c => c.ID).Any(i => MOVIE_CATS.Contains(i))){
|
||||
logger.Warn(string.Format("Rejected a request to {0} which does not support searching for movies.", indexer.DisplayName));
|
||||
return Request.CreateResponse(HttpStatusCode.Forbidden, "This indexer does not support movies.");
|
||||
}
|
||||
|
||||
var year = 0;
|
||||
|
||||
if (string.IsNullOrWhiteSpace(request.search))
|
||||
{
|
||||
// We are searching by IMDB id so look up the name
|
||||
var omdbapiRequest = new Utils.Clients.WebRequest("http://www.omdbapi.com/?type=movie&i=" + request.imdbid);
|
||||
omdbapiRequest.Encoding = Encoding.UTF8;
|
||||
var response = await webClient.GetString(omdbapiRequest);
|
||||
if (response.Status == HttpStatusCode.OK)
|
||||
{
|
||||
JObject result = JObject.Parse(response.Content);
|
||||
if (result["Title"] != null)
|
||||
{
|
||||
request.search = result["Title"].ToString();
|
||||
year = ParseUtil.CoerceInt(result["Year"].ToString());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var torznabQuery = new TorznabQuery()
|
||||
{
|
||||
ApiKey = request.passkey,
|
||||
Categories = MOVIE_CATS,
|
||||
SearchTerm = request.search,
|
||||
ImdbID = request.imdbid,
|
||||
QueryType = "TorrentPotato"
|
||||
};
|
||||
|
||||
IEnumerable<ReleaseInfo> releases = new List<ReleaseInfo>();
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(torznabQuery.SanitizedSearchTerm))
|
||||
{
|
||||
releases = await indexer.PerformQuery(torznabQuery);
|
||||
releases = indexer.CleanLinks(releases);
|
||||
}
|
||||
|
||||
// Cache non query results
|
||||
if (string.IsNullOrEmpty(torznabQuery.SanitizedSearchTerm))
|
||||
{
|
||||
cacheService.CacheRssResults(indexer, releases);
|
||||
}
|
||||
|
||||
releases = indexer.FilterResults(torznabQuery, releases);
|
||||
var serverUrl = string.Format("{0}://{1}:{2}{3}", Request.RequestUri.Scheme, Request.RequestUri.Host, Request.RequestUri.Port, serverService.BasePath());
|
||||
var potatoResponse = new TorrentPotatoResponse();
|
||||
|
||||
releases = TorznabUtil.FilterResultsToTitle(releases, torznabQuery.SanitizedSearchTerm, year);
|
||||
releases = TorznabUtil.FilterResultsToImdb(releases, request.imdbid);
|
||||
|
||||
foreach (var r in releases)
|
||||
{
|
||||
var release = Mapper.Map<ReleaseInfo>(r);
|
||||
release.Link = serverService.ConvertToProxyLink(release.Link, serverUrl, indexerID, "dl", release.Title + ".torrent");
|
||||
|
||||
// Only accept torrent links, magnet is not supported
|
||||
// This seems to be no longer the case, allowing magnet URIs for now
|
||||
if (release.Link != null || release.MagnetUri != null)
|
||||
{
|
||||
potatoResponse.results.Add(new TorrentPotatoResponseItem()
|
||||
{
|
||||
release_name = release.Title + "[" + indexer.DisplayName + "]", // Suffix the indexer so we can see which tracker we are using in CPS as it just says torrentpotato >.>
|
||||
torrent_id = release.Guid.ToString(),
|
||||
details_url = release.Comments.ToString(),
|
||||
download_url = (release.Link != null ? release.Link.ToString() : release.MagnetUri.ToString()),
|
||||
imdb_id = release.Imdb.HasValue ? "tt" + release.Imdb : null,
|
||||
freeleech = (release.DownloadVolumeFactor == 0 ? true : false),
|
||||
type = "movie",
|
||||
size = (long)release.Size / (1024 * 1024), // This is in MB
|
||||
leechers = (int)release.Peers - (int)release.Seeders,
|
||||
seeders = (int)release.Seeders,
|
||||
publish_date = r.PublishDate == DateTime.MinValue ? null : release.PublishDate.ToUniversalTime().ToString("s")
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// Log info
|
||||
if (string.IsNullOrWhiteSpace(torznabQuery.SanitizedSearchTerm))
|
||||
{
|
||||
logger.Info(string.Format("Found {0} torrentpotato releases from {1}", releases.Count(), indexer.DisplayName));
|
||||
}
|
||||
else
|
||||
{
|
||||
logger.Info(string.Format("Found {0} torrentpotato releases from {1} for: {2}", releases.Count(), indexer.DisplayName, torznabQuery.GetQueryString()));
|
||||
}
|
||||
|
||||
// Force the return as Json
|
||||
return new HttpResponseMessage()
|
||||
{
|
||||
Content = new JsonContent(potatoResponse)
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
379
src/Jackett/Controllers/ResultsController.cs
Normal file
379
src/Jackett/Controllers/ResultsController.cs
Normal file
@@ -0,0 +1,379 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.Linq;
|
||||
using System.Net;
|
||||
using System.Net.Http;
|
||||
using System.Text;
|
||||
using System.Text.RegularExpressions;
|
||||
using System.Threading.Tasks;
|
||||
using System.Web;
|
||||
using System.Web.Http;
|
||||
using System.Web.Http.Controllers;
|
||||
using System.Web.Http.Filters;
|
||||
using System.Xml.Linq;
|
||||
using Jackett.Indexers;
|
||||
using Jackett.Models;
|
||||
using Jackett.Services;
|
||||
using Jackett.Utils;
|
||||
using Jackett.Utils.Clients;
|
||||
using Newtonsoft.Json;
|
||||
using NLog;
|
||||
|
||||
namespace Jackett.Controllers.V20
|
||||
{
|
||||
public class RequiresApiKeyAttribute : AuthorizationFilterAttribute
|
||||
{
|
||||
public override void OnAuthorization(HttpActionContext actionContext)
|
||||
{
|
||||
var validApiKey = Engine.Server.Config.APIKey;
|
||||
var queryParams = actionContext.Request.GetQueryNameValuePairs().ToDictionary();
|
||||
var queryApiKey = queryParams.ContainsKey("apikey") ? queryParams["apikey"] : null;
|
||||
queryApiKey = queryParams.ContainsKey("passkey") ? queryParams["passkey"] : queryApiKey;
|
||||
|
||||
#if DEBUG
|
||||
if (Debugger.IsAttached)
|
||||
return;
|
||||
#endif
|
||||
if (queryApiKey != validApiKey)
|
||||
actionContext.Response = actionContext.Request.CreateResponse(HttpStatusCode.Unauthorized);
|
||||
}
|
||||
}
|
||||
|
||||
public class RequiresConfiguredIndexerAttribute : ActionFilterAttribute
|
||||
{
|
||||
public override void OnActionExecuting(HttpActionContext actionContext)
|
||||
{
|
||||
var controller = actionContext.ControllerContext.Controller;
|
||||
if (!(controller is IIndexerController))
|
||||
return;
|
||||
|
||||
var indexerController = controller as IIndexerController;
|
||||
|
||||
var parameters = actionContext.RequestContext.RouteData.Values;
|
||||
|
||||
if (!parameters.ContainsKey("indexerId"))
|
||||
{
|
||||
indexerController.CurrentIndexer = null;
|
||||
actionContext.Response = actionContext.Request.CreateErrorResponse(HttpStatusCode.Unauthorized, "Invalid parameter");
|
||||
return;
|
||||
}
|
||||
|
||||
var indexerId = parameters["indexerId"] as string;
|
||||
if (indexerId.IsNullOrEmptyOrWhitespace())
|
||||
{
|
||||
indexerController.CurrentIndexer = null;
|
||||
actionContext.Response = actionContext.Request.CreateErrorResponse(HttpStatusCode.Unauthorized, "Invalid parameter");
|
||||
return;
|
||||
}
|
||||
|
||||
var indexerService = indexerController.IndexerService;
|
||||
var indexer = indexerService.GetIndexer(indexerId);
|
||||
|
||||
if (indexer == null)
|
||||
{
|
||||
indexerController.CurrentIndexer = null;
|
||||
actionContext.Response = actionContext.Request.CreateErrorResponse(HttpStatusCode.Unauthorized, "Invalid parameter");
|
||||
return;
|
||||
}
|
||||
|
||||
if (!indexer.IsConfigured)
|
||||
{
|
||||
indexerController.CurrentIndexer = null;
|
||||
actionContext.Response = actionContext.Request.CreateErrorResponse(HttpStatusCode.Unauthorized, "Indexer is not configured");
|
||||
return;
|
||||
}
|
||||
|
||||
indexerController.CurrentIndexer = indexer;
|
||||
}
|
||||
}
|
||||
|
||||
public class RequiresValidQueryAttribute : RequiresConfiguredIndexerAttribute
|
||||
{
|
||||
public override void OnActionExecuting(HttpActionContext actionContext)
|
||||
{
|
||||
base.OnActionExecuting(actionContext);
|
||||
if (actionContext.Response != null)
|
||||
return;
|
||||
|
||||
var controller = actionContext.ControllerContext.Controller;
|
||||
if (!(controller is IResultController))
|
||||
return;
|
||||
|
||||
var resultController = controller as IResultController;
|
||||
|
||||
var query = actionContext.ActionArguments.First().Value;
|
||||
var queryType = query.GetType();
|
||||
var converter = queryType.GetMethod("ToTorznabQuery", System.Reflection.BindingFlags.Static | System.Reflection.BindingFlags.Public);
|
||||
if (converter == null)
|
||||
actionContext.Response = actionContext.Request.CreateErrorResponse(HttpStatusCode.BadRequest, "");
|
||||
var converted = converter.Invoke(null, new object[] { query });
|
||||
var torznabQuery = converted as TorznabQuery;
|
||||
resultController.CurrentQuery = torznabQuery;
|
||||
|
||||
if (!resultController.CurrentIndexer.CanHandleQuery(resultController.CurrentQuery))
|
||||
actionContext.Response = actionContext.Request.CreateErrorResponse(HttpStatusCode.BadRequest, $"{resultController.CurrentIndexer.ID} does not support the requested query.");
|
||||
}
|
||||
}
|
||||
|
||||
public class JsonResponseAttribute : ActionFilterAttribute
|
||||
{
|
||||
public override void OnActionExecuted(HttpActionExecutedContext actionExecutedContext)
|
||||
{
|
||||
base.OnActionExecuted(actionExecutedContext);
|
||||
|
||||
var content = actionExecutedContext.Response.Content as ObjectContent;
|
||||
actionExecutedContext.Response.Content = new JsonContent(content.Value);
|
||||
}
|
||||
}
|
||||
|
||||
public interface IResultController : IIndexerController
|
||||
{
|
||||
TorznabQuery CurrentQuery { get; set; }
|
||||
}
|
||||
|
||||
[AllowAnonymous]
|
||||
[JackettAPINoCache]
|
||||
[RoutePrefix("api/v2.0/indexers")]
|
||||
[RequiresApiKey]
|
||||
[RequiresValidQuery]
|
||||
public class ResultsController : ApiController, IResultController
|
||||
{
|
||||
public IIndexerManagerService IndexerService { get; private set; }
|
||||
public IIndexer CurrentIndexer { get; set; }
|
||||
public TorznabQuery CurrentQuery { get; set; }
|
||||
|
||||
public ResultsController(IIndexerManagerService indexerManagerService, IServerService ss, ICacheService c, Logger logger)
|
||||
{
|
||||
IndexerService = indexerManagerService;
|
||||
serverService = ss;
|
||||
cacheService = c;
|
||||
this.logger = logger;
|
||||
}
|
||||
|
||||
[HttpGet]
|
||||
public async Task<Models.DTO.ManualSearchResult> Results([FromUri]Models.DTO.ApiSearch request)
|
||||
{
|
||||
var trackers = IndexerService.GetAllIndexers().Where(t => t.IsConfigured);
|
||||
if (CurrentIndexer.ID != "all")
|
||||
trackers = trackers.Where(t => t.ID == CurrentIndexer.ID).ToList();
|
||||
trackers = trackers.Where(t => t.IsConfigured && t.CanHandleQuery(CurrentQuery));
|
||||
|
||||
var tasks = trackers.ToList().Select(t => t.ResultsForQuery(CurrentQuery)).ToList();
|
||||
var aggregateTask = Task.WhenAll(tasks);
|
||||
|
||||
await aggregateTask;
|
||||
|
||||
var results = tasks.Where(t => t.Status == TaskStatus.RanToCompletion).Where(t => t.Result.Count() > 0).SelectMany(t =>
|
||||
{
|
||||
var searchResults = t.Result;
|
||||
var indexer = searchResults.First().Origin;
|
||||
cacheService.CacheRssResults(indexer, searchResults);
|
||||
|
||||
return searchResults.Select(result =>
|
||||
{
|
||||
var item = AutoMapper.Mapper.Map<TrackerCacheResult>(result);
|
||||
item.Tracker = indexer.DisplayName;
|
||||
item.TrackerId = indexer.ID;
|
||||
item.Peers = item.Peers - item.Seeders; // Use peers as leechers
|
||||
|
||||
return item;
|
||||
});
|
||||
}).OrderByDescending(d => d.PublishDate).ToList();
|
||||
|
||||
ConfigureCacheResults(results);
|
||||
|
||||
var manualResult = new Models.DTO.ManualSearchResult()
|
||||
{
|
||||
Results = results,
|
||||
Indexers = trackers.Select(t => t.DisplayName).ToList()
|
||||
};
|
||||
|
||||
|
||||
if (manualResult.Indexers.Count() == 0)
|
||||
manualResult.Indexers = new List<string>() { "None" };
|
||||
|
||||
logger.Info(string.Format("Manual search for \"{0}\" on {1} with {2} results.", CurrentQuery.SanitizedSearchTerm, string.Join(", ", manualResult.Indexers), manualResult.Results.Count()));
|
||||
return manualResult;
|
||||
}
|
||||
|
||||
[HttpGet]
|
||||
public async Task<IHttpActionResult> Torznab([FromUri]Models.DTO.TorznabRequest request)
|
||||
{
|
||||
if (string.Equals(CurrentQuery.QueryType, "caps", StringComparison.InvariantCultureIgnoreCase))
|
||||
{
|
||||
return ResponseMessage(new HttpResponseMessage()
|
||||
{
|
||||
Content = new StringContent(CurrentIndexer.TorznabCaps.ToXml(), Encoding.UTF8, "application/xml")
|
||||
});
|
||||
}
|
||||
|
||||
if (CurrentQuery.ImdbID != null)
|
||||
{
|
||||
if (CurrentQuery.QueryType != "movie")
|
||||
{
|
||||
logger.Warn($"A non movie request with an imdbid was made from {Request.GetOwinContext().Request.RemoteIpAddress}.");
|
||||
return GetErrorXML(201, "Incorrect parameter: only movie-search supports the imdbid parameter");
|
||||
}
|
||||
|
||||
if (!string.IsNullOrEmpty(CurrentQuery.SearchTerm))
|
||||
{
|
||||
logger.Warn($"A movie-search request from {Request.GetOwinContext().Request.RemoteIpAddress} was made contining q and imdbid.");
|
||||
return GetErrorXML(201, "Incorrect parameter: please specify either imdbid or q");
|
||||
}
|
||||
|
||||
CurrentQuery.ImdbID = ParseUtil.GetFullImdbID(CurrentQuery.ImdbID); // normalize ImdbID
|
||||
if (CurrentQuery.ImdbID == null)
|
||||
{
|
||||
logger.Warn($"A movie-search request from {Request.GetOwinContext().Request.RemoteIpAddress} was made with an invalid imdbid.");
|
||||
return GetErrorXML(201, "Incorrect parameter: invalid imdbid format");
|
||||
}
|
||||
|
||||
if (!CurrentIndexer.TorznabCaps.SupportsImdbSearch)
|
||||
{
|
||||
logger.Warn($"A movie-search request with imdbid from {Request.GetOwinContext().Request.RemoteIpAddress} was made but the indexer {CurrentIndexer.DisplayName} doesn't support it.");
|
||||
return GetErrorXML(203, "Function Not Available: imdbid is not supported by this indexer");
|
||||
}
|
||||
}
|
||||
|
||||
var releases = await CurrentIndexer.ResultsForQuery(CurrentQuery);
|
||||
|
||||
// Some trackers do not support multiple category filtering so filter the releases that match manually.
|
||||
int? newItemCount = null;
|
||||
|
||||
// Cache non query results
|
||||
if (string.IsNullOrEmpty(CurrentQuery.SanitizedSearchTerm))
|
||||
{
|
||||
newItemCount = cacheService.GetNewItemCount(CurrentIndexer, releases);
|
||||
cacheService.CacheRssResults(CurrentIndexer, releases);
|
||||
}
|
||||
|
||||
// Log info
|
||||
var logBuilder = new StringBuilder();
|
||||
if (newItemCount != null)
|
||||
{
|
||||
logBuilder.AppendFormat("Found {0} ({1} new) releases from {2}", releases.Count(), newItemCount, CurrentIndexer.DisplayName);
|
||||
}
|
||||
else
|
||||
{
|
||||
logBuilder.AppendFormat("Found {0} releases from {1}", releases.Count(), CurrentIndexer.DisplayName);
|
||||
}
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(CurrentQuery.SanitizedSearchTerm))
|
||||
{
|
||||
logBuilder.AppendFormat(" for: {0}", CurrentQuery.GetQueryString());
|
||||
}
|
||||
|
||||
logger.Info(logBuilder.ToString());
|
||||
|
||||
var serverUrl = string.Format("{0}://{1}:{2}{3}", Request.RequestUri.Scheme, Request.RequestUri.Host, Request.RequestUri.Port, serverService.BasePath());
|
||||
var resultPage = new ResultPage(new ChannelInfo
|
||||
{
|
||||
Title = CurrentIndexer.DisplayName,
|
||||
Description = CurrentIndexer.DisplayDescription,
|
||||
Link = new Uri(CurrentIndexer.SiteLink),
|
||||
ImageUrl = new Uri(serverUrl + "logos/" + CurrentIndexer.ID + ".png"),
|
||||
ImageTitle = CurrentIndexer.DisplayName,
|
||||
ImageLink = new Uri(CurrentIndexer.SiteLink),
|
||||
ImageDescription = CurrentIndexer.DisplayName
|
||||
});
|
||||
|
||||
var proxiedReleases = releases.Select(r => AutoMapper.Mapper.Map<ReleaseInfo>(r)).Select(r =>
|
||||
{
|
||||
r.Link = serverService.ConvertToProxyLink(r.Link, serverUrl, r.Origin.ID, "dl", r.Title + ".torrent");
|
||||
return r;
|
||||
});
|
||||
|
||||
resultPage.Releases = proxiedReleases.ToList();
|
||||
|
||||
var xml = resultPage.ToXml(new Uri(serverUrl));
|
||||
// Force the return as XML
|
||||
return ResponseMessage(new HttpResponseMessage()
|
||||
{
|
||||
Content = new StringContent(xml, Encoding.UTF8, "application/rss+xml")
|
||||
});
|
||||
}
|
||||
|
||||
public IHttpActionResult GetErrorXML(int code, string description)
|
||||
{
|
||||
var xdoc = new XDocument(
|
||||
new XDeclaration("1.0", "UTF-8", null),
|
||||
new XElement("error",
|
||||
new XAttribute("code", code.ToString()),
|
||||
new XAttribute("description", description)
|
||||
)
|
||||
);
|
||||
|
||||
var xml = xdoc.Declaration.ToString() + Environment.NewLine + xdoc.ToString();
|
||||
|
||||
return ResponseMessage(new HttpResponseMessage()
|
||||
{
|
||||
Content = new StringContent(xml, Encoding.UTF8, "application/xml")
|
||||
});
|
||||
}
|
||||
|
||||
[HttpGet]
|
||||
[JsonResponse]
|
||||
public async Task<Models.DTO.TorrentPotatoResponse> Potato([FromUri]Models.DTO.TorrentPotatoRequest request)
|
||||
{
|
||||
var releases = await CurrentIndexer.ResultsForQuery(CurrentQuery);
|
||||
|
||||
// Cache non query results
|
||||
if (string.IsNullOrEmpty(CurrentQuery.SanitizedSearchTerm))
|
||||
cacheService.CacheRssResults(CurrentIndexer, releases);
|
||||
|
||||
// Log info
|
||||
if (string.IsNullOrWhiteSpace(CurrentQuery.SanitizedSearchTerm))
|
||||
logger.Info($"Found {releases.Count()} torrentpotato releases from {CurrentIndexer.DisplayName}");
|
||||
else
|
||||
logger.Info($"Found {releases.Count()} torrentpotato releases from {CurrentIndexer.DisplayName} for: {CurrentQuery.GetQueryString()}");
|
||||
|
||||
var serverUrl = string.Format("{0}://{1}:{2}{3}", Request.RequestUri.Scheme, Request.RequestUri.Host, Request.RequestUri.Port, serverService.BasePath());
|
||||
var potatoReleases = releases.Where(r => r.Link != null || r.MagnetUri != null).Select(r =>
|
||||
{
|
||||
var release = AutoMapper.Mapper.Map<ReleaseInfo>(r);
|
||||
release.Link = serverService.ConvertToProxyLink(release.Link, serverUrl, CurrentIndexer.ID, "dl", release.Title + ".torrent");
|
||||
var item = new Models.DTO.TorrentPotatoResponseItem()
|
||||
{
|
||||
release_name = release.Title + "[" + CurrentIndexer.DisplayName + "]", // Suffix the indexer so we can see which tracker we are using in CPS as it just says torrentpotato >.>
|
||||
torrent_id = release.Guid.ToString(),
|
||||
details_url = release.Comments.ToString(),
|
||||
download_url = (release.Link != null ? release.Link.ToString() : release.MagnetUri.ToString()),
|
||||
imdb_id = release.Imdb.HasValue ? "tt" + release.Imdb : null,
|
||||
freeleech = (release.DownloadVolumeFactor == 0 ? true : false),
|
||||
type = "movie",
|
||||
size = (long)release.Size / (1024 * 1024), // This is in MB
|
||||
leechers = (int)release.Peers - (int)release.Seeders,
|
||||
seeders = (int)release.Seeders,
|
||||
publish_date = r.PublishDate == DateTime.MinValue ? null : release.PublishDate.ToUniversalTime().ToString("s")
|
||||
};
|
||||
return item;
|
||||
});
|
||||
|
||||
var potatoResponse = new Models.DTO.TorrentPotatoResponse()
|
||||
{
|
||||
results = potatoReleases.ToList()
|
||||
};
|
||||
|
||||
return potatoResponse;
|
||||
}
|
||||
|
||||
private void ConfigureCacheResults(IEnumerable<TrackerCacheResult> results)
|
||||
{
|
||||
var serverUrl = string.Format("{0}://{1}:{2}{3}", Request.RequestUri.Scheme, Request.RequestUri.Host, Request.RequestUri.Port, serverService.BasePath());
|
||||
foreach (var result in results)
|
||||
{
|
||||
var link = result.Link;
|
||||
var file = StringUtil.MakeValidFileName(result.Title, '_', false) + ".torrent";
|
||||
result.Link = serverService.ConvertToProxyLink(link, serverUrl, result.TrackerId, "dl", file);
|
||||
if (result.Link != null && result.Link.Scheme != "magnet" && !string.IsNullOrWhiteSpace(Engine.Server.Config.BlackholeDir))
|
||||
result.BlackholeLink = serverService.ConvertToProxyLink(link, serverUrl, result.TrackerId, "bh", file);
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
private Logger logger;
|
||||
private IServerService serverService;
|
||||
private ICacheService cacheService;
|
||||
}
|
||||
}
|
||||
169
src/Jackett/Controllers/ServerConfigurationController.cs
Normal file
169
src/Jackett/Controllers/ServerConfigurationController.cs
Normal file
@@ -0,0 +1,169 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
using System.Web.Http;
|
||||
using Jackett.Models;
|
||||
using Jackett.Services;
|
||||
using Jackett.Utils;
|
||||
using NLog;
|
||||
|
||||
namespace Jackett.Controllers.V20
|
||||
{
|
||||
[RoutePrefix("api/v2.0/server")]
|
||||
[JackettAuthorized]
|
||||
[JackettAPINoCache]
|
||||
public class ServerConfigurationController : ApiController
|
||||
{
|
||||
public ServerConfigurationController(IConfigurationService c, IServerService s, IProcessService p, IIndexerManagerService i, ISecuityService ss, IUpdateService u, ILogCacheService lc, Logger l)
|
||||
{
|
||||
config = c;
|
||||
serverService = s;
|
||||
processService = p;
|
||||
indexerService = i;
|
||||
securityService = ss;
|
||||
updater = u;
|
||||
logCache = lc;
|
||||
logger = l;
|
||||
}
|
||||
|
||||
[HttpPost]
|
||||
public void AdminPassword([FromBody]string password)
|
||||
{
|
||||
var oldPassword = serverService.Config.AdminPassword;
|
||||
if (string.IsNullOrEmpty(password))
|
||||
password = string.Empty;
|
||||
|
||||
if (oldPassword != password)
|
||||
{
|
||||
serverService.Config.AdminPassword = securityService.HashPassword(password);
|
||||
serverService.SaveConfig();
|
||||
}
|
||||
}
|
||||
|
||||
[HttpPost]
|
||||
public void Update()
|
||||
{
|
||||
updater.CheckForUpdatesNow();
|
||||
}
|
||||
|
||||
[HttpGet]
|
||||
public Models.DTO.ServerConfig Config()
|
||||
{
|
||||
|
||||
var dto = new Models.DTO.ServerConfig(serverService.notices, serverService.Config, config.GetVersion());
|
||||
return dto;
|
||||
}
|
||||
|
||||
[ActionName("Config")]
|
||||
[HttpPost]
|
||||
public void UpdateConfig([FromBody]Models.DTO.ServerConfig config)
|
||||
{
|
||||
var originalPort = Engine.Server.Config.Port;
|
||||
var originalAllowExternal = Engine.Server.Config.AllowExternal;
|
||||
int port = config.port;
|
||||
bool external = config.external;
|
||||
string saveDir = config.blackholedir;
|
||||
bool updateDisabled = config.updatedisabled;
|
||||
bool preRelease = config.prerelease;
|
||||
bool logging = config.logging;
|
||||
string basePathOverride = config.basepathoverride;
|
||||
string omdbApiKey = config.omdbkey;
|
||||
|
||||
Engine.Server.Config.UpdateDisabled = updateDisabled;
|
||||
Engine.Server.Config.UpdatePrerelease = preRelease;
|
||||
Engine.Server.Config.BasePathOverride = basePathOverride;
|
||||
Startup.BasePath = Engine.Server.BasePath();
|
||||
Engine.Server.SaveConfig();
|
||||
|
||||
Engine.SetLogLevel(logging ? LogLevel.Debug : LogLevel.Info);
|
||||
Startup.TracingEnabled = logging;
|
||||
|
||||
if (omdbApiKey != Engine.Server.Config.OmdbApiKey)
|
||||
{
|
||||
Engine.Server.Config.OmdbApiKey = omdbApiKey;
|
||||
Engine.Server.SaveConfig();
|
||||
// HACK
|
||||
indexerService.InitAggregateIndexer();
|
||||
}
|
||||
|
||||
if (port != Engine.Server.Config.Port || external != Engine.Server.Config.AllowExternal)
|
||||
{
|
||||
|
||||
if (ServerUtil.RestrictedPorts.Contains(port))
|
||||
throw new Exception("The port you have selected is restricted, try a different one.");
|
||||
|
||||
if (port < 1 || port > 65535)
|
||||
throw new Exception("The port you have selected is invalid, it must be below 65535.");
|
||||
|
||||
// Save port to the config so it can be picked up by the if needed when running as admin below.
|
||||
Engine.Server.Config.AllowExternal = external;
|
||||
Engine.Server.Config.Port = port;
|
||||
Engine.Server.SaveConfig();
|
||||
|
||||
// On Windows change the url reservations
|
||||
if (System.Environment.OSVersion.Platform != PlatformID.Unix)
|
||||
{
|
||||
if (!ServerUtil.IsUserAdministrator())
|
||||
{
|
||||
try
|
||||
{
|
||||
processService.StartProcessAndLog(System.Windows.Forms.Application.ExecutablePath, "--ReserveUrls", true);
|
||||
}
|
||||
catch
|
||||
{
|
||||
Engine.Server.Config.Port = originalPort;
|
||||
Engine.Server.Config.AllowExternal = originalAllowExternal;
|
||||
Engine.Server.SaveConfig();
|
||||
|
||||
throw new Exception("Failed to acquire admin permissions to reserve the new port.");
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
serverService.ReserveUrls(true);
|
||||
}
|
||||
}
|
||||
|
||||
(new Thread(() =>
|
||||
{
|
||||
Thread.Sleep(500);
|
||||
serverService.Stop();
|
||||
Engine.BuildContainer();
|
||||
Engine.Server.Initalize();
|
||||
Engine.Server.Start();
|
||||
})).Start();
|
||||
}
|
||||
|
||||
if (saveDir != Engine.Server.Config.BlackholeDir)
|
||||
{
|
||||
if (!string.IsNullOrEmpty(saveDir))
|
||||
{
|
||||
if (!Directory.Exists(saveDir))
|
||||
{
|
||||
throw new Exception("Blackhole directory does not exist");
|
||||
}
|
||||
}
|
||||
|
||||
Engine.Server.Config.BlackholeDir = saveDir;
|
||||
Engine.Server.SaveConfig();
|
||||
}
|
||||
}
|
||||
|
||||
[HttpGet]
|
||||
public List<CachedLog> Logs()
|
||||
{
|
||||
return logCache.Logs;
|
||||
}
|
||||
|
||||
private IConfigurationService config;
|
||||
private IServerService serverService;
|
||||
private IProcessService processService;
|
||||
private IIndexerManagerService indexerService;
|
||||
private ISecuityService securityService;
|
||||
private IUpdateService updater;
|
||||
private ILogCacheService logCache;
|
||||
private Logger logger;
|
||||
}
|
||||
}
|
||||
@@ -1,180 +0,0 @@
|
||||
using AutoMapper;
|
||||
using Jackett.Models;
|
||||
using Jackett.Services;
|
||||
using Jackett.Utils;
|
||||
using NLog;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.Linq;
|
||||
using System.Net;
|
||||
using System.Net.Http;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using System.Web;
|
||||
using System.Web.Http;
|
||||
using System.Xml.Linq;
|
||||
|
||||
namespace Jackett.Controllers
|
||||
{
|
||||
[AllowAnonymous]
|
||||
[JackettAPINoCache]
|
||||
public class TorznabController : ApiController
|
||||
{
|
||||
private IIndexerManagerService indexerService;
|
||||
private Logger logger;
|
||||
private IServerService serverService;
|
||||
private ICacheService cacheService;
|
||||
|
||||
public TorznabController(IIndexerManagerService i, Logger l, IServerService s, ICacheService c)
|
||||
{
|
||||
indexerService = i;
|
||||
logger = l;
|
||||
serverService = s;
|
||||
cacheService = c;
|
||||
}
|
||||
|
||||
public HttpResponseMessage GetErrorXML(int code, string description)
|
||||
{
|
||||
var xdoc = new XDocument(
|
||||
new XDeclaration("1.0", "UTF-8", null),
|
||||
new XElement("error",
|
||||
new XAttribute("code", code.ToString()),
|
||||
new XAttribute("description", description)
|
||||
)
|
||||
);
|
||||
|
||||
var xml = xdoc.Declaration.ToString() + Environment.NewLine + xdoc.ToString();
|
||||
|
||||
return new HttpResponseMessage()
|
||||
{
|
||||
Content = new StringContent(xml, Encoding.UTF8, "application/xml")
|
||||
};
|
||||
}
|
||||
|
||||
[HttpGet]
|
||||
public async Task<HttpResponseMessage> Call(string indexerID)
|
||||
{
|
||||
var indexer = indexerService.GetIndexer(indexerID);
|
||||
var torznabQuery = TorznabQuery.FromHttpQuery(HttpUtility.ParseQueryString(Request.RequestUri.Query));
|
||||
|
||||
if (string.Equals(torznabQuery.QueryType, "caps", StringComparison.InvariantCultureIgnoreCase))
|
||||
{
|
||||
return new HttpResponseMessage()
|
||||
{
|
||||
Content = new StringContent(indexer.TorznabCaps.ToXml(), Encoding.UTF8, "application/xml")
|
||||
};
|
||||
}
|
||||
|
||||
torznabQuery.ExpandCatsToSubCats();
|
||||
var allowBadApiDueToDebug = false;
|
||||
#if DEBUG
|
||||
allowBadApiDueToDebug = Debugger.IsAttached;
|
||||
#endif
|
||||
|
||||
if (!allowBadApiDueToDebug && !string.Equals(torznabQuery.ApiKey, serverService.Config.APIKey, StringComparison.InvariantCultureIgnoreCase))
|
||||
{
|
||||
logger.Warn(string.Format("A request from {0} was made with an incorrect API key.", Request.GetOwinContext().Request.RemoteIpAddress));
|
||||
return Request.CreateResponse(HttpStatusCode.Forbidden, "Incorrect API key");
|
||||
}
|
||||
|
||||
if (!indexer.IsConfigured)
|
||||
{
|
||||
logger.Warn(string.Format("Rejected a request to {0} which is unconfigured.", indexer.DisplayName));
|
||||
return Request.CreateResponse(HttpStatusCode.Forbidden, "This indexer is not configured.");
|
||||
}
|
||||
|
||||
if (torznabQuery.ImdbID != null)
|
||||
{
|
||||
if (torznabQuery.QueryType != "movie")
|
||||
{
|
||||
logger.Warn(string.Format("A non movie request with an imdbid was made from {0}.", Request.GetOwinContext().Request.RemoteIpAddress));
|
||||
return GetErrorXML(201, "Incorrect parameter: only movie-search supports the imdbid parameter");
|
||||
}
|
||||
|
||||
if (!string.IsNullOrEmpty(torznabQuery.SearchTerm))
|
||||
{
|
||||
logger.Warn(string.Format("A movie-search request from {0} was made contining q and imdbid.", Request.GetOwinContext().Request.RemoteIpAddress));
|
||||
return GetErrorXML(201, "Incorrect parameter: please specify either imdbid or q");
|
||||
}
|
||||
|
||||
torznabQuery.ImdbID = ParseUtil.GetFullImdbID(torznabQuery.ImdbID); // normalize ImdbID
|
||||
if (torznabQuery.ImdbID == null)
|
||||
{
|
||||
logger.Warn(string.Format("A movie-search request from {0} was made with an invalid imdbid.", Request.GetOwinContext().Request.RemoteIpAddress));
|
||||
return GetErrorXML(201, "Incorrect parameter: invalid imdbid format");
|
||||
}
|
||||
|
||||
if (!indexer.TorznabCaps.SupportsImdbSearch)
|
||||
{
|
||||
logger.Warn(string.Format("A movie-search request with imdbid from {0} was made but the indexer {1} doesn't support it.", Request.GetOwinContext().Request.RemoteIpAddress, indexer.DisplayName));
|
||||
return GetErrorXML(203, "Function Not Available: imdbid is not supported by this indexer");
|
||||
}
|
||||
}
|
||||
|
||||
var releases = await indexer.PerformQuery(torznabQuery);
|
||||
releases = indexer.CleanLinks(releases);
|
||||
|
||||
// Some trackers do not keep their clocks up to date and can be ~20 minutes out!
|
||||
foreach (var release in releases.Where(r => r.PublishDate > DateTime.Now))
|
||||
{
|
||||
release.PublishDate = DateTime.Now;
|
||||
}
|
||||
|
||||
// Some trackers do not support multiple category filtering so filter the releases that match manually.
|
||||
var filteredReleases = releases = indexer.FilterResults(torznabQuery, releases);
|
||||
int? newItemCount = null;
|
||||
|
||||
// Cache non query results
|
||||
if (string.IsNullOrEmpty(torznabQuery.SanitizedSearchTerm))
|
||||
{
|
||||
newItemCount = cacheService.GetNewItemCount(indexer, filteredReleases);
|
||||
cacheService.CacheRssResults(indexer, releases);
|
||||
}
|
||||
|
||||
// Log info
|
||||
var logBuilder = new StringBuilder();
|
||||
if (newItemCount != null) {
|
||||
logBuilder.AppendFormat(string.Format("Found {0} ({1} new) releases from {2}", releases.Count(), newItemCount, indexer.DisplayName));
|
||||
}
|
||||
else {
|
||||
logBuilder.AppendFormat(string.Format("Found {0} releases from {1}", releases.Count(), indexer.DisplayName));
|
||||
}
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(torznabQuery.SanitizedSearchTerm)) {
|
||||
logBuilder.AppendFormat(" for: {0}", torznabQuery.GetQueryString());
|
||||
}
|
||||
|
||||
logger.Info(logBuilder.ToString());
|
||||
|
||||
var serverUrl = string.Format("{0}://{1}:{2}{3}", Request.RequestUri.Scheme, Request.RequestUri.Host, Request.RequestUri.Port, serverService.BasePath());
|
||||
var resultPage = new ResultPage(new ChannelInfo
|
||||
{
|
||||
Title = indexer.DisplayName,
|
||||
Description = indexer.DisplayDescription,
|
||||
Link = new Uri(indexer.SiteLink),
|
||||
ImageUrl = new Uri(serverUrl + "logos/" + indexer.ID + ".png"),
|
||||
ImageTitle = indexer.DisplayName,
|
||||
ImageLink = new Uri(indexer.SiteLink),
|
||||
ImageDescription = indexer.DisplayName
|
||||
});
|
||||
|
||||
|
||||
foreach(var result in releases)
|
||||
{
|
||||
var clone = Mapper.Map<ReleaseInfo>(result);
|
||||
clone.Link = serverService.ConvertToProxyLink(clone.Link, serverUrl, indexerID, "dl", result.Title + ".torrent");
|
||||
resultPage.Releases.Add(clone);
|
||||
}
|
||||
|
||||
var xml = resultPage.ToXml(new Uri(serverUrl));
|
||||
// Force the return as XML
|
||||
return new HttpResponseMessage()
|
||||
{
|
||||
Content = new StringContent(xml, Encoding.UTF8, "application/rss+xml")
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
89
src/Jackett/Controllers/UIController.cs
Normal file
89
src/Jackett/Controllers/UIController.cs
Normal file
@@ -0,0 +1,89 @@
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Net;
|
||||
using System.Net.Http;
|
||||
using System.Net.Http.Headers;
|
||||
using System.Threading.Tasks;
|
||||
using System.Web;
|
||||
using System.Web.Http;
|
||||
using Jackett.Services;
|
||||
using Jackett.Utils;
|
||||
using NLog;
|
||||
|
||||
namespace Jackett.Controllers
|
||||
{
|
||||
[RoutePrefix("UI")]
|
||||
[JackettAuthorized]
|
||||
[JackettAPINoCache]
|
||||
public class WebUIController : ApiController
|
||||
{
|
||||
public WebUIController(IConfigurationService config, IServerService ss, ISecuityService s, Logger l)
|
||||
{
|
||||
this.config = config;
|
||||
serverService = ss;
|
||||
securityService = s;
|
||||
logger = l;
|
||||
}
|
||||
|
||||
private HttpResponseMessage GetFile(string path)
|
||||
{
|
||||
var result = new HttpResponseMessage(HttpStatusCode.OK);
|
||||
var mappedPath = Path.Combine(config.GetContentFolder(), path);
|
||||
var stream = new FileStream(mappedPath, FileMode.Open, FileAccess.Read, FileShare.Read);
|
||||
result.Content = new StreamContent(stream);
|
||||
result.Content.Headers.ContentType = new MediaTypeHeaderValue(MimeMapping.GetMimeMapping(mappedPath));
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
[HttpGet]
|
||||
[AllowAnonymous]
|
||||
public IHttpActionResult Logout()
|
||||
{
|
||||
var ctx = Request.GetOwinContext();
|
||||
var authManager = ctx.Authentication;
|
||||
authManager.SignOut("ApplicationCookie");
|
||||
return Redirect("UI/Dashboard");
|
||||
}
|
||||
|
||||
[HttpGet]
|
||||
[HttpPost]
|
||||
[AllowAnonymous]
|
||||
public async Task<HttpResponseMessage> Dashboard()
|
||||
{
|
||||
if (Request.RequestUri.Query != null && Request.RequestUri.Query.Contains("logout"))
|
||||
{
|
||||
var file = GetFile("login.html");
|
||||
securityService.Logout(file);
|
||||
return file;
|
||||
}
|
||||
|
||||
|
||||
if (securityService.CheckAuthorised(Request))
|
||||
{
|
||||
return GetFile("index.html");
|
||||
|
||||
}
|
||||
else
|
||||
{
|
||||
var formData = await Request.Content.ReadAsFormDataAsync();
|
||||
|
||||
if (formData != null && securityService.HashPassword(formData["password"]) == serverService.Config.AdminPassword)
|
||||
{
|
||||
var file = GetFile("index.html");
|
||||
securityService.Login(file);
|
||||
return file;
|
||||
}
|
||||
else
|
||||
{
|
||||
return GetFile("login.html");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private IConfigurationService config;
|
||||
private IServerService serverService;
|
||||
private ISecuityService securityService;
|
||||
private Logger logger;
|
||||
}
|
||||
}
|
||||
261
src/Jackett/Definitions/b2s-share.yml
Normal file
261
src/Jackett/Definitions/b2s-share.yml
Normal file
@@ -0,0 +1,261 @@
|
||||
---
|
||||
site: b2s-share
|
||||
name: B2S-Share
|
||||
language: pt-br
|
||||
type: private
|
||||
encoding: iso-8859-1
|
||||
links:
|
||||
- http://www.b2s-share.com/
|
||||
|
||||
caps:
|
||||
categorymappings:
|
||||
#3D
|
||||
- {id: 141, cat: Movies/3D, desc: "3D Ação"}
|
||||
- {id: 142, cat: Movies/3D, desc: "3D Animação"}
|
||||
- {id: 143, cat: Movies/3D, desc: "3D Aventura"}
|
||||
- {id: 144, cat: Movies/3D, desc: "3D Clássico"}
|
||||
- {id: 145, cat: Movies/3D, desc: "3D Comédia"}
|
||||
- {id: 146, cat: Movies/3D, desc: "3D Documentário"}
|
||||
- {id: 147, cat: Movies/3D, desc: "3D Drama"}
|
||||
- {id: 149, cat: Movies/3D, desc: "3D Ficção"}
|
||||
- {id: 150, cat: Movies/3D, desc: "3D Guerra"}
|
||||
- {id: 151, cat: Movies/3D, desc: "3D Infantil"}
|
||||
- {id: 152, cat: Movies/3D, desc: "3D Musical"}
|
||||
- {id: 154, cat: Movies/3D, desc: "3D Outros"}
|
||||
- {id: 155, cat: Movies/3D, desc: "3D Policial"}
|
||||
- {id: 156, cat: Movies/3D, desc: "3D Religioso"}
|
||||
- {id: 157, cat: Movies/3D, desc: "3D Romance"}
|
||||
- {id: 158, cat: Movies/3D, desc: "3D Shows"}
|
||||
- {id: 159, cat: Movies/3D, desc: "3D Suspense"}
|
||||
- {id: 160, cat: Movies/3D, desc: "3D Terror"}
|
||||
- {id: 161, cat: Movies/3D, desc: "3D Thriller"}
|
||||
- {id: 162, cat: Movies/3D, desc: "3D Western"}
|
||||
|
||||
#4K
|
||||
- {id: 173, cat: Movies, desc: "4K Ação"}
|
||||
- {id: 191, cat: Movies, desc: "4K Animação"}
|
||||
- {id: 190, cat: Movies, desc: "4K Aventura"}
|
||||
- {id: 188, cat: Movies, desc: "4K Clássico"}
|
||||
- {id: 175, cat: Movies, desc: "4K Comédia"}
|
||||
- {id: 186, cat: Movies, desc: "4K Documentário"}
|
||||
- {id: 185, cat: Movies, desc: "4K Drama"}
|
||||
- {id: 177, cat: Movies, desc: "4K Ficção"}
|
||||
- {id: 178, cat: Movies, desc: "4K Guerra"}
|
||||
- {id: 179, cat: Movies, desc: "4K Infantil"}
|
||||
- {id: 180, cat: Movies, desc: "4K Musical"}
|
||||
- {id: 184, cat: Movies, desc: "4K Outros"}
|
||||
- {id: 176, cat: Movies, desc: "4K Policial"}
|
||||
- {id: 187, cat: Movies, desc: "4K Religioso"}
|
||||
- {id: 174, cat: Movies, desc: "4K Romance"}
|
||||
- {id: 181, cat: Movies, desc: "4K Shows"}
|
||||
- {id: 182, cat: Movies, desc: "4K Suspense"}
|
||||
- {id: 189, cat: Movies, desc: "4K Terror"}
|
||||
- {id: 183, cat: Movies, desc: "4K Thriller"}
|
||||
|
||||
#ANIME
|
||||
- {id: 11, cat: TV/Anime, desc: "Anime"}
|
||||
|
||||
#PC-APPS
|
||||
- {id: 2, cat: PC, desc: "PC APPS - Linux"}
|
||||
- {id: 3, cat: PC, desc: "PC APPS - Mac"}
|
||||
- {id: 33, cat: PC, desc: "PC APPS - Portateis"}
|
||||
- {id: 1, cat: PC, desc: "PC APPS - Windows"}
|
||||
|
||||
#BD-R
|
||||
- {id: 140, cat: Movies, desc: "BD-R Autorado"}
|
||||
- {id: 119, cat: Movies, desc: "BD-R"}
|
||||
|
||||
#MOBILE
|
||||
- {id: 163, cat: Other, desc: "Mobile App/Jogos-Android"}
|
||||
- {id: 164, cat: Other, desc: "Mobile App/Jogos-iPhone"}
|
||||
- {id: 93, cat: Other, desc: "Mobile App/Jogos-Java"}
|
||||
- {id: 169, cat: Other, desc: "Mobile App/Jogos-Outros"}
|
||||
- {id: 168, cat: Other, desc: "Mobile App/Jogos-Win"}
|
||||
- {id: 92, cat: Other, desc: "Mobile Filmes"}
|
||||
- {id: 118, cat: Other, desc: "Mobile Séries"}
|
||||
- {id: 94, cat: Other, desc: "Mobile Wallpapers"}
|
||||
|
||||
#CARTOON
|
||||
- {id: 172, cat: TV/Other, desc: "Desenho Animado"}
|
||||
|
||||
#OTHER
|
||||
- {id: 4, cat: Other, desc: "Apostilas/Cursos"}
|
||||
- {id: 12, cat: Books, desc: "Diversos"}
|
||||
- {id: 167, cat: Books, desc: "E-book/Livros"}
|
||||
- {id: 7, cat: PC/Games, desc: "Emuladores / Roms"}
|
||||
- {id: 166, cat: Books/Comics, desc: "HQ"}
|
||||
- {id: 165, cat: Books, desc: "Revistas"}
|
||||
|
||||
#DVD-R
|
||||
- {id: 41, cat: Movies/SD, desc: "DVD-R Autorado"}
|
||||
- {id: 32, cat: Movies/SD, desc: "DVD-R"}
|
||||
|
||||
#MOVIES
|
||||
- {id: 14, cat: Movies, desc: "Filmes Ação"}
|
||||
- {id: 99, cat: Movies, desc: "Filmes Animação"}
|
||||
- {id: 15, cat: Movies, desc: "Filmes Aventura"}
|
||||
- {id: 16, cat: Movies, desc: "Filmes Clássico"}
|
||||
- {id: 17, cat: Movies, desc: "Filmes Comédia"}
|
||||
- {id: 31, cat: Movies, desc: "Filmes Documentário"}
|
||||
- {id: 18, cat: Movies, desc: "Filmes Drama"}
|
||||
- {id: 19, cat: Movies, desc: "Filmes Ficção"}
|
||||
- {id: 20, cat: Movies, desc: "Filmes Guerra"}
|
||||
- {id: 126, cat: Movies, desc: "Filmes Infantil"}
|
||||
- {id: 96, cat: Movies, desc: "Filmes Musical"}
|
||||
- {id: 24, cat: Movies, desc: "Filmes Outros"}
|
||||
- {id: 40, cat: Movies, desc: "Filmes Policial"}
|
||||
- {id: 39, cat: Movies, desc: "Filmes Religioso"}
|
||||
- {id: 30, cat: Movies, desc: "Filmes Romance"}
|
||||
- {id: 22, cat: Movies, desc: "Filmes Suspense"}
|
||||
- {id: 23, cat: Movies, desc: "Filmes Terror"}
|
||||
- {id: 130, cat: Movies, desc: "Filmes Thriller"}
|
||||
- {id: 131, cat: Movies, desc: "Filmes Western"}
|
||||
- {id: 34, cat: Movies, desc: "Filmes x264"}
|
||||
|
||||
#HD-1080p
|
||||
- {id: 73, cat: Movies/HD, desc: "Filmes 1080p Ação"}
|
||||
- {id: 87, cat: Movies/HD, desc: "Filmes 1080p Animação"}
|
||||
- {id: 74, cat: Movies/HD, desc: "Filmes 1080p Aventura"}
|
||||
- {id: 75, cat: Movies/HD, desc: "Filmes 1080p Clássico"}
|
||||
- {id: 76, cat: Movies/HD, desc: "Filmes 1080p Comédia"}
|
||||
- {id: 77, cat: Movies/HD, desc: "Filmes 1080p Documentário"}
|
||||
- {id: 78, cat: Movies/HD, desc: "Filmes 1080p Drama"}
|
||||
- {id: 79, cat: Movies/HD, desc: "Filmes 1080p Ficção"}
|
||||
- {id: 80, cat: Movies/HD, desc: "Filmes 1080p Guerra"}
|
||||
- {id: 127, cat: Movies/HD, desc: "Filmes 1080p Infantil"}
|
||||
- {id: 97, cat: Movies/HD, desc: "Filmes 1080p Musical"}
|
||||
- {id: 63, cat: Movies/HD, desc: "Filmes 1080p Outros"}
|
||||
- {id: 82, cat: Movies/HD, desc: "Filmes 1080p Policial"}
|
||||
- {id: 83, cat: Movies/HD, desc: "Filmes 1080p Religioso"}
|
||||
- {id: 84, cat: Movies/HD, desc: "Filmes 1080p Romance"}
|
||||
- {id: 88, cat: Movies/HD, desc: "Filmes 1080p Shows"}
|
||||
- {id: 85, cat: Movies/HD, desc: "Filmes 1080p Suspense"}
|
||||
- {id: 86, cat: Movies/HD, desc: "Filmes 1080p Terror"}
|
||||
- {id: 132, cat: Movies/HD, desc: "Filmes 1080p Thriller"}
|
||||
- {id: 135, cat: Movies/HD, desc: "Filmes 1080p Western"}
|
||||
|
||||
#HD-720p
|
||||
- {id: 58, cat: Movies/HD, desc: "Filmes 720p Ação"}
|
||||
- {id: 89, cat: Movies/HD, desc: "Filmes 720p Animação"}
|
||||
- {id: 60, cat: Movies/HD, desc: "Filmes 720p Aventura"}
|
||||
- {id: 61, cat: Movies/HD, desc: "Filmes 720p Clássico"}
|
||||
- {id: 62, cat: Movies/HD, desc: "Filmes 720p Comédia"}
|
||||
- {id: 72, cat: Movies/HD, desc: "Filmes 720p Documentário"}
|
||||
- {id: 64, cat: Movies/HD, desc: "Filmes 720p Drama"}
|
||||
- {id: 65, cat: Movies/HD, desc: "Filmes 720p Ficção"}
|
||||
- {id: 66, cat: Movies/HD, desc: "Filmes 720p Guerra"}
|
||||
- {id: 129, cat: Movies/HD, desc: "Filmes 720p Infantil"}
|
||||
- {id: 98, cat: Movies/HD, desc: "Filmes 720p Musical"}
|
||||
- {id: 59, cat: Movies/HD, desc: "Filmes 720p Outros"}
|
||||
- {id: 70, cat: Movies/HD, desc: "Filmes 720p Policial"}
|
||||
- {id: 57, cat: Movies/HD, desc: "Filmes 720p Religioso"}
|
||||
- {id: 71, cat: Movies/HD, desc: "Filmes 720p Romance"}
|
||||
- {id: 90, cat: Movies/HD, desc: "Filmes 720p Shows"}
|
||||
- {id: 68, cat: Movies/HD, desc: "Filmes 720p Suspense"}
|
||||
- {id: 69, cat: Movies/HD, desc: "Filmes 720p Terror"}
|
||||
- {id: 134, cat: Movies/HD, desc: "Filmes 720p Thriller"}
|
||||
- {id: 137, cat: Movies/HD, desc: "Filmes 720p Western"}
|
||||
|
||||
#GAMES
|
||||
- {id: 6, cat: Console, desc: "Jogos Console"}
|
||||
- {id: 51, cat: PC/Games, desc: "Jogos Emuladores"}
|
||||
- {id: 44, cat: Console, desc: "Jogos Gamecube"}
|
||||
- {id: 43, cat: Console/NDS, desc: "Jogos Nintendo DS"}
|
||||
- {id: 52, cat: Console, desc: "Jogos Outros"}
|
||||
- {id: 5, cat: PC/Games, desc: "Jogos PC"}
|
||||
- {id: 47, cat: Console, desc: "Jogos PS2"}
|
||||
- {id: 48, cat: Console, desc: "Jogos PS3"}
|
||||
- {id: 170, cat: Console, desc: "Jogos PS4"}
|
||||
- {id: 46, cat: Console, desc: "Jogos PSP"}
|
||||
- {id: 45, cat: Console/Wii, desc: "Jogos Wii"}
|
||||
- {id: 49, cat: Console/Xbox, desc: "Jogos XBOX"}
|
||||
- {id: 50, cat: Console/Xbox 360, desc: "Jogos XBOX360"}
|
||||
- {id: 171, cat: Console, desc: "Jogos XBOXONE"}
|
||||
|
||||
#MUSIC
|
||||
- {id: 29, cat: Audio, desc: "Musica Discografia"}
|
||||
- {id: 28, cat: Audio, desc: "Musica Gospel"}
|
||||
- {id: 139, cat: Audio, desc: "Musica Infantil"}
|
||||
- {id: 27, cat: Audio, desc: "Musica Internacionais"}
|
||||
- {id: 26, cat: Audio, desc: "Musica Nacionais"}
|
||||
- {id: 91, cat: Audio, desc: "Musica Outros"}
|
||||
|
||||
#TV-SERIES
|
||||
- {id: 35, cat: TV/HD, desc: "Séries HD"}
|
||||
- {id: 8, cat: TV, desc: "Séries"}
|
||||
|
||||
#SHOWS
|
||||
- {id: 13, cat: Audio/Video, desc: "Show"}
|
||||
- {id: 56, cat: Audio/Video, desc: "Video Clipes"}
|
||||
|
||||
#TV
|
||||
- {id: 53, cat: TV, desc: "Entretenimento"}
|
||||
- {id: 54, cat: TV/Sport, desc: "Esportes"}
|
||||
- {id: 138, cat: TV, desc: "Infantil"}
|
||||
- {id: 55, cat: TV, desc: "Jornalismo"}
|
||||
|
||||
#XXX
|
||||
- {id: 36, cat: XXX/Other, desc: "Anime Hentai"}
|
||||
- {id: 10, cat: XXX, desc: "Filmes Adultos"}
|
||||
- {id: 37, cat: XXX/Other, desc: "XXX Fotos"}
|
||||
- {id: 95, cat: XXX/Other, desc: "XXX Revistas"}
|
||||
|
||||
modes:
|
||||
search: [q]
|
||||
|
||||
login:
|
||||
path: account-login.php
|
||||
method: post
|
||||
inputs:
|
||||
username: "{{ .Config.username }}"
|
||||
password: "{{ .Config.password }}"
|
||||
test:
|
||||
path: torrents.php
|
||||
|
||||
search:
|
||||
path: torrents-search.php
|
||||
inputs:
|
||||
search: "{{ .Query.Keywords }}"
|
||||
rows:
|
||||
selector: table[class^="ttable_headinner"] > tbody > tr:has(a[href^="torrents-details.php?id="])
|
||||
fields:
|
||||
details:
|
||||
selector: a[href^="torrents-details.php?id="]
|
||||
attribute: href
|
||||
title:
|
||||
selector: a[href^="torrents-details.php?id="]
|
||||
download:
|
||||
selector: a[href^="torrents-details.php?id="]
|
||||
attribute: href
|
||||
filters:
|
||||
- name: replace
|
||||
args: ["torrents-details.php", "download.php"]
|
||||
category:
|
||||
selector: a[href^="torrents.php?cat="]
|
||||
attribute: href
|
||||
filters:
|
||||
- name: querystring
|
||||
args: cat
|
||||
date:
|
||||
selector: td:nth-child(2) > span
|
||||
filters:
|
||||
- name: replace
|
||||
args: ["(", ""]
|
||||
- name: replace
|
||||
args: [")", ""]
|
||||
size:
|
||||
selector: td:nth-child(3)
|
||||
grabs:
|
||||
selector: td:nth-child(4) > font > b
|
||||
seeders:
|
||||
selector: td:nth-child(5) > b > font
|
||||
leechers:
|
||||
selector: td:nth-child(6) > font > b
|
||||
downloadvolumefactor:
|
||||
case:
|
||||
"img[alt=\"[free]\"]": "0"
|
||||
"*": "1"
|
||||
uploadvolumefactor:
|
||||
case:
|
||||
"img[alt=\"[+UP x2]\"]": "2"
|
||||
"*": "1"
|
||||
83
src/Jackett/Definitions/blutopia.yml
Normal file
83
src/Jackett/Definitions/blutopia.yml
Normal file
@@ -0,0 +1,83 @@
|
||||
---
|
||||
site: blutopia
|
||||
name: Blutopia
|
||||
description: "HD Movie tracker"
|
||||
language: en-us
|
||||
type: private
|
||||
encoding: UTF-8
|
||||
links:
|
||||
- https://blutopia.xyz/
|
||||
|
||||
caps:
|
||||
categorymappings:
|
||||
- {id: 1, cat: Movies, desc: "Movies"}
|
||||
- {id: 2, cat: TV, desc: "TV"}
|
||||
- {id: 3, cat: Movies, desc: "FANRES"}
|
||||
|
||||
modes:
|
||||
search: [q]
|
||||
|
||||
login:
|
||||
path: /login
|
||||
method: form
|
||||
inputs:
|
||||
username: "{{ .Config.username }}"
|
||||
password: "{{ .Config.password }}"
|
||||
error:
|
||||
- selector: table.main:contains("Login Failed!")
|
||||
test:
|
||||
path: /torrents
|
||||
|
||||
download:
|
||||
selector: a[href^="/download.php/"]
|
||||
|
||||
search:
|
||||
path: /torrents/search
|
||||
inputs:
|
||||
$raw: "{{range .Categories}}filter_cat[{{.}}]=1&{{end}}"
|
||||
name: "{{ .Query.Keywords }}"
|
||||
category_id: 1
|
||||
type: BD50
|
||||
order: created_at:desc
|
||||
rows:
|
||||
selector: table.table > tbody > tr
|
||||
fields:
|
||||
# category:
|
||||
# selector: a[href^="categories"]
|
||||
# attribute: href
|
||||
title:
|
||||
remove: ul
|
||||
selector: td:nth-child(2)
|
||||
download:
|
||||
selector: a.view-torrent
|
||||
attribute: href
|
||||
filters:
|
||||
- name: replace
|
||||
args: ["/torrents/", "/download/"]
|
||||
details:
|
||||
selector: a.view-torrent
|
||||
attribute: href
|
||||
size:
|
||||
selector: td:nth-child(4)
|
||||
seeders:
|
||||
selector: td:nth-child(6)
|
||||
leechers:
|
||||
selector: td:nth-child(7)
|
||||
grabs:
|
||||
selector: td:nth-child(5)
|
||||
filters:
|
||||
- name: regexp
|
||||
args: (\d+)
|
||||
date:
|
||||
selector: td:nth-child(3)
|
||||
filters:
|
||||
- name: append
|
||||
args: " ago"
|
||||
downloadvolumefactor:
|
||||
case:
|
||||
#"i[data-original-title=\"100% Free\"]": "0"
|
||||
"*": "1"
|
||||
uploadvolumefactor:
|
||||
case:
|
||||
#"i[data-original-title=\"Double upload\"]": "2"
|
||||
"*": "1"
|
||||
88
src/Jackett/Definitions/cpabien.yml
Normal file
88
src/Jackett/Definitions/cpabien.yml
Normal file
@@ -0,0 +1,88 @@
|
||||
---
|
||||
site: cpasbien
|
||||
name: cpasbien
|
||||
language: fr-fr
|
||||
type: public
|
||||
encoding: UTF-8
|
||||
links:
|
||||
- http://cpabien.cc/
|
||||
|
||||
caps:
|
||||
categorymappings:
|
||||
- {id: films, cat: Movies, desc: "Movies"}
|
||||
- {id: series, cat: TV, desc: "TV"}
|
||||
|
||||
modes:
|
||||
search: [q]
|
||||
tv-search: [q, season, ep]
|
||||
|
||||
settings: []
|
||||
|
||||
download:
|
||||
selector: "#telecharger"
|
||||
|
||||
search:
|
||||
path: "/search.php?t={{ .Query.Keywords }}"
|
||||
rows:
|
||||
selector: div[class^='ligne']
|
||||
fields:
|
||||
site_date:
|
||||
selector: a
|
||||
filters:
|
||||
# date is at the end of the title, so we get it and name it site_date
|
||||
- name: regexp
|
||||
args: "(\\w+)$"
|
||||
title:
|
||||
selector: a
|
||||
filters:
|
||||
# now we put the date at the right place according scene naming rules using .Result.site_date
|
||||
- name: replace
|
||||
args: ["FRENCH", "{{ .Result.site_date }} FRENCH"]
|
||||
- name: replace
|
||||
args: ["TRUEFRENCH", "{{ .Result.site_date }} TRUEFRENCH"]
|
||||
- name: replace
|
||||
args: ["VOSTFR", "{{ .Result.site_date }} VOSTFR"]
|
||||
# and we delete it at the end
|
||||
- name: re_replace
|
||||
args: ["(\\w+)$", ""]
|
||||
details:
|
||||
selector: a
|
||||
attribute: href
|
||||
download:
|
||||
selector: a
|
||||
attribute: href
|
||||
size:
|
||||
selector: div.poid
|
||||
filters:
|
||||
- name: re_replace
|
||||
args: [ "\\.(\\d) Ko", "$1X00"]
|
||||
- name: re_replace
|
||||
args: [ " Ko", "000"]
|
||||
- name: re_replace
|
||||
args: [ "\\.(\\d) Mo", "$1X00000"]
|
||||
- name: re_replace
|
||||
args: [ " Mo", "000000"]
|
||||
- name: re_replace
|
||||
args: [ "\\.(\\d) Go", "$1X00000000"]
|
||||
- name: re_replace
|
||||
args: [ " Go", "000000000"]
|
||||
- name: re_replace
|
||||
args: [ "\\.(\\d) To", "$1X00000000000"]
|
||||
- name: re_replace
|
||||
args: [ " To", "000000000000"]
|
||||
- name: replace
|
||||
args: [ "X", "" ]
|
||||
seeders:
|
||||
text: 0
|
||||
seeders:
|
||||
selector: div.up
|
||||
optional: true
|
||||
leechers:
|
||||
text: 0
|
||||
leechers:
|
||||
selector: div.down
|
||||
optional: true
|
||||
downloadvolumefactor:
|
||||
text: "0"
|
||||
uploadvolumefactor:
|
||||
text: "1"
|
||||
@@ -69,9 +69,13 @@
|
||||
rows:
|
||||
selector: tr:has(a.tname)
|
||||
fields:
|
||||
title:
|
||||
title-attribute:
|
||||
selector: a.tname
|
||||
attribute: title
|
||||
title-text:
|
||||
selector: a.tname
|
||||
title:
|
||||
text: "{{if .Result.title-attribute }}{{ .Result.title-attribute }}{{else}}{{ .Result.title-text }}{{end}}"
|
||||
details:
|
||||
selector: a.tname
|
||||
attribute: href
|
||||
@@ -85,12 +89,12 @@
|
||||
selector: a[href^="/download.php/"]
|
||||
attribute: href
|
||||
grabs:
|
||||
selector: td:nth-child(7)
|
||||
selector: td:nth-child(8)
|
||||
filters:
|
||||
- name: regexp
|
||||
args: (\d+)
|
||||
size:
|
||||
selector: td:nth-child(6)
|
||||
selector: td:nth-child(7)
|
||||
date:
|
||||
selector: td:nth-child(2) > right > div:has(font:contains("Uploaded"))
|
||||
remove: div > font
|
||||
@@ -98,15 +102,19 @@
|
||||
- name: trim
|
||||
args: ":"
|
||||
seeders:
|
||||
selector: td:nth-child(8)
|
||||
leechers:
|
||||
selector: td:nth-child(9)
|
||||
leechers:
|
||||
selector: td:nth-child(10)
|
||||
banner:
|
||||
selector: a.tname
|
||||
attribute: onmouseover
|
||||
filters:
|
||||
- name: regexp
|
||||
args: src=([^\s]+)
|
||||
imdb:
|
||||
selector: a[href^="http://www.imdb.com/title/"]
|
||||
optional: true
|
||||
attribute: href
|
||||
downloadvolumefactor:
|
||||
case:
|
||||
"img[src=\"pic/free.gif\"]": "0"
|
||||
|
||||
@@ -7,6 +7,16 @@
|
||||
links:
|
||||
- http://freedom-paradise.eu/
|
||||
|
||||
download:
|
||||
before:
|
||||
path: "list_thanks.php"
|
||||
method: "get"
|
||||
inputs:
|
||||
id: "{{ .DownloadUri.Query.id }}"
|
||||
to: "give"
|
||||
torrent: "{{ .DownloadUri.Query.id }}"
|
||||
|
||||
|
||||
caps:
|
||||
categorymappings:
|
||||
# ANIMES
|
||||
|
||||
@@ -6,6 +6,8 @@
|
||||
encoding: UTF-8
|
||||
links:
|
||||
- https://ilcorsaronero.info/
|
||||
certificates:
|
||||
- FFB230323B987B07C24DCC13BC892CF30536F015 # incomplete CA chain
|
||||
|
||||
caps:
|
||||
categorymappings:
|
||||
|
||||
@@ -95,15 +95,18 @@
|
||||
|
||||
login:
|
||||
method: post
|
||||
path: "new/account-login.php"
|
||||
path: "/new/account-login.php"
|
||||
inputs:
|
||||
username: "{{ .Config.username }}"
|
||||
passkey: "{{ .Config.passkey }}"
|
||||
error:
|
||||
- selector: font.error:contains("Access Denied")
|
||||
test:
|
||||
path: index.php
|
||||
path: "/new/index.php"
|
||||
selector: a[href="account-logout.php"]
|
||||
|
||||
search:
|
||||
path: "/new/torrents-search.php"
|
||||
path: "/new/{{if .Query.Keywords}}search-torrents.php{{else}}torrents.php{{end}}"
|
||||
inputs:
|
||||
$raw: "{{range .Categories}}c{{.}}=1&{{end}}"
|
||||
search: "\"{{ .Query.Keywords }}\""
|
||||
@@ -200,8 +203,6 @@
|
||||
date:
|
||||
selector: td:nth-child(3)
|
||||
filters:
|
||||
- name: append
|
||||
args: " +00:00"
|
||||
- name: dateparse
|
||||
args: "02-01-2006 15:04:05 -07:00"
|
||||
seeders:
|
||||
|
||||
253
src/Jackett/Definitions/manicomioshare.yml
Normal file
253
src/Jackett/Definitions/manicomioshare.yml
Normal file
@@ -0,0 +1,253 @@
|
||||
---
|
||||
site: manicomioshare
|
||||
name: Manicomio Share
|
||||
language: pt-br
|
||||
type: private
|
||||
encoding: iso-8859-1
|
||||
links:
|
||||
- https://www.manicomio-share.com
|
||||
|
||||
caps:
|
||||
categorymappings:
|
||||
#XXX
|
||||
- {id: 80, cat: XXX, desc: "Adulto: [XXX] - Filmes"}
|
||||
- {id: 157, cat: XXX, desc: "Adulto: [XXX] - Blu-Ray"}
|
||||
- {id: 159, cat: XXX, desc: "Adulto: [XXX] - DVD-R"}
|
||||
- {id: 160, cat: XXX, desc: "Adulto: [XXX] - DVD-R 9"}
|
||||
- {id: 161, cat: XXX, desc: "Adulto: [XXX] - Eróticos"}
|
||||
- {id: 162, cat: XXX, desc: "Adulto: [XXX] - HD"}
|
||||
- {id: 188, cat: XXX, desc: "Adulto: [XXX] - 4K"}
|
||||
- {id: 112, cat: XXX/Other, desc: "Adulto: [XXX] - Fotos"}
|
||||
- {id: 113, cat: XXX/Other, desc: "Adulto: [XXX] - Hentai"}
|
||||
- {id: 131, cat: XXX/Other, desc: "Adulto: [XXX] - Jogos"}
|
||||
|
||||
#ANIME
|
||||
- {id: 21, cat: TV/Anime, desc: "Anime: Anime"}
|
||||
- {id: 155, cat: TV/Anime, desc: "Anime: Anime DVD-R"}
|
||||
- {id: 156, cat: TV/Anime, desc: "Anime: Anime HD"}
|
||||
|
||||
#PC APPS
|
||||
- {id: 22, cat: PC, desc: "Aplicativos: Linux"}
|
||||
- {id: 23, cat: PC, desc: "Aplicativos: Mac OS"}
|
||||
- {id: 24, cat: PC, desc: "Aplicativos: Windows"}
|
||||
|
||||
#BOOKLET
|
||||
- {id: 25, cat: Other, desc: "Apostila: Apostila e Textos"}
|
||||
- {id: 169, cat: Other, desc: "Apostila: Cursos e Video Aula"}
|
||||
|
||||
#MOBILE
|
||||
- {id: 26, cat: Other, desc: "Celular: Aplicativos"}
|
||||
- {id: 184, cat: Other, desc: "Celular: Jogos"}
|
||||
|
||||
#TV-CHILD
|
||||
- {id: 88, cat: TV/Other, desc: "Desenhos: Desenhos"}
|
||||
- {id: 165, cat: TV/Other, desc: "Desenhos: Desenhos DVD-R"}
|
||||
- {id: 166, cat: TV/Other, desc: "Desenhos: Desenhos HD"}
|
||||
- {id: 83, cat: TV/Other, desc: "Diversos: Diversos"}
|
||||
- {id: 84, cat: TV/Other, desc: "Educativos: Infantil"}
|
||||
|
||||
#TV-SPORTS
|
||||
- {id: 105, cat: TV/Sport, desc: "Eventos: Esportivos"}
|
||||
- {id: 153, cat: TV/Sport, desc: "Eventos: Esportivos DVD-R"}
|
||||
- {id: 154, cat: TV/Sport, desc: "Eventos: Esportivos HD"}
|
||||
|
||||
#MOVIES
|
||||
- {id: 189, cat: Movies, desc: "Filmes: 4K"}
|
||||
- {id: 132, cat: Movies/BluRay, desc: "Filmes: Blu-Ray"}
|
||||
- {id: 152, cat: Movies/BluRay, desc: "Filmes: Blu-Ray Nacionais"}
|
||||
- {id: 141, cat: Movies/BluRay, desc: "Filmes: Blu-Ray 3D"}
|
||||
- {id: 142, cat: Movies/BluRay, desc: "Filmes: Blu-Ray BD25"}
|
||||
- {id: 182, cat: Movies/BluRay, desc: "Filmes: Blu-Ray BD25 Nacionais"}
|
||||
- {id: 183, cat: Movies/BluRay, desc: "Filmes: Blu-Ray BD25 3D"}
|
||||
- {id: 143, cat: Movies/BluRay, desc: "Filmes: Blu-Ray Remux"}
|
||||
- {id: 34, cat: Movies/SD, desc: "Filmes: DVD-R"}
|
||||
- {id: 134, cat: Movies/SD, desc: "Filmes: DVD-R Nacionais"}
|
||||
- {id: 144, cat: Movies/SD, desc: "Filmes: DVD-R 9"}
|
||||
- {id: 145, cat: Movies/Other, desc: "Filmes: Documentarios DVD-R"}
|
||||
- {id: 151, cat: Movies/Other, desc: "Filmes: Documentarios HD"}
|
||||
- {id: 127, cat: Movies/HD, desc: "Filmes: HD"}
|
||||
- {id: 148, cat: Movies/Foreign, desc: "Filmes: HD Nacionais"}
|
||||
- {id: 147, cat: Movies/3D, desc: "Filmes: HD 3D"}
|
||||
- {id: 128, cat: Movies/Foreign, desc: "Filmes: Nacionais"}
|
||||
- {id: 27, cat: Movies, desc: "Filmes: Ação"}
|
||||
- {id: 95, cat: Movies, desc: "Filmes: Animação"}
|
||||
- {id: 28, cat: Movies, desc: "Filmes: Aventura"}
|
||||
- {id: 29, cat: Movies, desc: "Filmes: Biografia"}
|
||||
- {id: 30, cat: Movies, desc: "Filmes: Classicos"}
|
||||
- {id: 31, cat: Movies, desc: "Filmes: Comédia"}
|
||||
- {id: 32, cat: Movies, desc: "Filmes: Documentarios"}
|
||||
- {id: 33, cat: Movies, desc: "Filmes: Drama"}
|
||||
- {id: 35, cat: Movies, desc: "Filmes: Esportes"}
|
||||
- {id: 185, cat: Movies, desc: "Filmes: Fantasia"}
|
||||
- {id: 36, cat: Movies, desc: "Filmes: Ficção"}
|
||||
- {id: 85, cat: Movies, desc: "Filmes: Guerra"}
|
||||
- {id: 37, cat: Movies, desc: "Filmes: Infantil"}
|
||||
- {id: 118, cat: Movies, desc: "Filmes: Musicais"}
|
||||
- {id: 104, cat: Movies, desc: "Filmes: Policial"}
|
||||
- {id: 40, cat: Movies, desc: "Filmes: Suspense"}
|
||||
- {id: 38, cat: Movies, desc: "Filmes: Religiosos"}
|
||||
- {id: 39, cat: Movies, desc: "Filmes: Romance"}
|
||||
- {id: 41, cat: Movies, desc: "Filmes: Terror"}
|
||||
- {id: 107, cat: Movies, desc: "Filmes: Western"}
|
||||
|
||||
#GAMES
|
||||
- {id: 97, cat: Console, desc: "Jogos: Dreamcast"}
|
||||
- {id: 44, cat: PC/Games, desc: "Jogos: Emuladores e Rom"}
|
||||
- {id: 101, cat: Console, desc: "Jogos: Game Cube"}
|
||||
- {id: 140, cat: PC/Mac, desc: "Jogos: Mac OS"}
|
||||
- {id: 119, cat: Console/NDS, desc: "Jogos: Nintendo DS"}
|
||||
- {id: 45, cat: PC/Games, desc: "Jogos: Pc"}
|
||||
- {id: 87, cat: Console, desc: "Jogos: PS1"}
|
||||
- {id: 46, cat: Console, desc: "Jogos: PS2"}
|
||||
- {id: 120, cat: Console, desc: "Jogos: PS3"}
|
||||
- {id: 82, cat: Console/PSP, desc: "Jogos: PSP"}
|
||||
- {id: 191, cat: Console, desc: "Jogos: PSVita"}
|
||||
- {id: 47, cat: Console/XBox, desc: "Jogos: Xbox"}
|
||||
- {id: 48, cat: Console/XBox 360, desc: "Jogos: Xbox 360"}
|
||||
- {id: 100, cat: Console/Wii, desc: "Jogos: Wii"}
|
||||
- {id: 187, cat: Console/Wii, desc: "Jogos: Wii-U"}
|
||||
|
||||
#EBOOKS
|
||||
- {id: 49, cat: Books/EBook, desc: "Livros: E-books"}
|
||||
|
||||
#MUSIC
|
||||
- {id: 50, cat: Audio, desc: "Músicas: Axé"}
|
||||
- {id: 51, cat: Audio, desc: "Músicas: Blues"}
|
||||
- {id: 52, cat: Audio, desc: "Músicas: Classica"}
|
||||
- {id: 53, cat: Audio, desc: "Músicas: Coletânea"}
|
||||
- {id: 103, cat: Audio, desc: "Músicas: Country"}
|
||||
- {id: 102, cat: Audio, desc: "Músicas: Discografia"}
|
||||
- {id: 54, cat: Audio, desc: "Músicas: Dance"}
|
||||
- {id: 55, cat: Audio, desc: "Músicas: Eletronica"}
|
||||
- {id: 135, cat: Audio, desc: "Músicas: Enka e Japonesa"}
|
||||
- {id: 56, cat: Audio, desc: "Músicas: Forró"}
|
||||
- {id: 57, cat: Audio, desc: "Músicas: Funk"}
|
||||
- {id: 58, cat: Audio, desc: "Músicas: Gospel"}
|
||||
- {id: 117, cat: Audio, desc: "Músicas: Hard Core"}
|
||||
- {id: 59, cat: Audio, desc: "Músicas: Hard Rock"}
|
||||
- {id: 61, cat: Audio, desc: "Músicas: Hip Hop"}
|
||||
- {id: 90, cat: Audio, desc: "Músicas: House"}
|
||||
- {id: 62, cat: Audio, desc: "Músicas: Infantil"}
|
||||
- {id: 175, cat: Audio, desc: "Músicas: Instrumental"}
|
||||
- {id: 86, cat: Audio, desc: "Músicas: Jazz"}
|
||||
- {id: 60, cat: Audio, desc: "Músicas: Metal"}
|
||||
- {id: 63, cat: Audio, desc: "Músicas: MPB"}
|
||||
- {id: 64, cat: Audio, desc: "Músicas: New Age"}
|
||||
- {id: 94, cat: Audio, desc: "Músicas: Oldies"}
|
||||
- {id: 65, cat: Audio, desc: "Músicas: Pagode"}
|
||||
- {id: 66, cat: Audio, desc: "Músicas: Pop"}
|
||||
- {id: 109, cat: Audio, desc: "Músicas: Psychedelic"}
|
||||
- {id: 67, cat: Audio, desc: "Músicas: Punk Rock"}
|
||||
- {id: 89, cat: Audio, desc: "Músicas: Raízes"}
|
||||
- {id: 68, cat: Audio, desc: "Músicas: Rap"}
|
||||
- {id: 69, cat: Audio, desc: "Músicas: Reggae"}
|
||||
- {id: 70, cat: Audio, desc: "Músicas: Regionais"}
|
||||
- {id: 71, cat: Audio, desc: "Músicas: Religiosas"}
|
||||
- {id: 72, cat: Audio, desc: "Músicas: Rock"}
|
||||
- {id: 73, cat: Audio, desc: "Músicas: Samba"}
|
||||
- {id: 74, cat: Audio, desc: "Músicas: Sertanejo"}
|
||||
- {id: 98, cat: Audio, desc: "Músicas: Soul R&B"}
|
||||
- {id: 110, cat: Audio, desc: "Músicas: Surf Music"}
|
||||
- {id: 92, cat: Audio, desc: "Músicas: Techno"}
|
||||
- {id: 91, cat: Audio, desc: "Músicas: Trance"}
|
||||
- {id: 75, cat: Audio, desc: "Músicas: Trilha Sonora"}
|
||||
- {id: 93, cat: Audio, desc: "Músicas: Vocal"}
|
||||
- {id: 111, cat: Audio, desc: "Músicas: World Music"}
|
||||
|
||||
#TV-FOREIGN
|
||||
- {id: 170, cat: TV/Foreign, desc: "Novelas: Novela"}
|
||||
- {id: 171, cat: TV/Foreign, desc: "Novelas: Novela DVD-R"}
|
||||
- {id: 172, cat: TV/Foreign, desc: "Novelas: Novela HD"}
|
||||
- {id: 179, cat: TV/Other, desc: "Religião: Religião Diversos"}
|
||||
- {id: 178, cat: TV/Other, desc: "Religião: Religião DVD-R"}
|
||||
|
||||
#BOOKS
|
||||
- {id: 96, cat: Books, desc: "Revistas: Revistas"}
|
||||
- {id: 99, cat: Books/Comics, desc: "Revistas: HQ"}
|
||||
- {id: 192, cat: Books/Comics, desc: "Revistas: Mangá"}
|
||||
|
||||
#TV-SERIES
|
||||
- {id: 76, cat: TV, desc: "Séries: Seriados"}
|
||||
- {id: 122, cat: TV/HD, desc: "Séries: HD"}
|
||||
- {id: 190, cat: TV, desc: "Séries: 4K"}
|
||||
- {id: 186, cat: TV, desc: "Séries: Blu-Ray"}
|
||||
- {id: 124, cat: TV/SD, desc: "Séries: DVD-R"}
|
||||
- {id: 181, cat: TV/SD, desc: "Séries: DVD-R 9"}
|
||||
- {id: 125, cat: TV/SD, desc: "Séries: DVDRip"}
|
||||
- {id: 123, cat: TV/Other, desc: "Séries: Cartoon"}
|
||||
- {id: 164, cat: TV/Other, desc: "Séries: Cartoon DVD-R"}
|
||||
- {id: 163, cat: TV/Other, desc: "Séries: Cartoon HD"}
|
||||
|
||||
#MUSIC SHOWS
|
||||
- {id: 77, cat: Audio/Video, desc: "Shows: Shows"}
|
||||
- {id: 133, cat: Audio/Video, desc: "Shows: Shows Blu-Ray"}
|
||||
- {id: 130, cat: Audio/Video, desc: "Shows: Shows DVD-R"}
|
||||
- {id: 180, cat: Audio/Video, desc: "Shows: Shows DVD-R 9"}
|
||||
- {id: 129, cat: Audio/Video, desc: "Shows: Shows HD"}
|
||||
|
||||
#TV-OTHER
|
||||
- {id: 78, cat: TV, desc: "Televisão: Televisão"}
|
||||
- {id: 136, cat: TV, desc: "Televisão: Televisão HDTV"}
|
||||
- {id: 137, cat: TV, desc: "Televisão: Televisão HD"}
|
||||
- {id: 139, cat: TV, desc: "Televisão: Televisão SDTV"}
|
||||
|
||||
#MUSIC CLIPS
|
||||
- {id: 79, cat: Audio/Video, desc: "Video: Video Clipes"}
|
||||
- {id: 167, cat: Audio/Video, desc: "Video: Video Clipes DVD-R"}
|
||||
- {id: 168, cat: Audio/Video, desc: "Video: Video Clipes HD"}
|
||||
|
||||
modes:
|
||||
search: [q]
|
||||
|
||||
settings:
|
||||
- name: cookie
|
||||
type: text
|
||||
label: Cookie
|
||||
|
||||
login:
|
||||
method: cookie
|
||||
inputs:
|
||||
cookie: "{{ .Config.cookie }}"
|
||||
test:
|
||||
path: index.php
|
||||
|
||||
search:
|
||||
path: pesquisa.php
|
||||
inputs:
|
||||
busca: "{{ .Query.Keywords }}"
|
||||
rows:
|
||||
selector: table#tbltorrent > tbody > tr[data-id]
|
||||
fields:
|
||||
download:
|
||||
selector: a[href*="/download.php?id="]
|
||||
attribute: href
|
||||
details:
|
||||
selector: a[href*="/item/"]
|
||||
attribute: href
|
||||
category:
|
||||
selector: a[href*="/torrents.php?cat="]
|
||||
attribute: href
|
||||
filters:
|
||||
- name: querystring
|
||||
args: cat
|
||||
title:
|
||||
selector: a[href*="/item/"]
|
||||
remove: font[color^=green]
|
||||
filters:
|
||||
- name: replace
|
||||
args: ["|", ""]
|
||||
size:
|
||||
selector: td:nth-child(4)
|
||||
grabs:
|
||||
selector: td:nth-child(6)
|
||||
seeders:
|
||||
selector: td:nth-child(5) > span:nth-child(1)
|
||||
leechers:
|
||||
selector: td:nth-child(5) > span:nth-child(2)
|
||||
downloadvolumefactor:
|
||||
case:
|
||||
"span.h3t:contains(\"[livre]\")": "0"
|
||||
"*": "1"
|
||||
uploadvolumefactor:
|
||||
case:
|
||||
"*": "1"
|
||||
@@ -6,7 +6,7 @@
|
||||
type: private
|
||||
encoding: windows-1252
|
||||
links:
|
||||
- http://new-retro.eu/
|
||||
- https://new-retro.eu/
|
||||
|
||||
caps:
|
||||
categorymappings:
|
||||
@@ -46,8 +46,9 @@
|
||||
tv-search: [q, season, ep]
|
||||
|
||||
login:
|
||||
path: /takelogin.php
|
||||
method: post
|
||||
path: /login.php
|
||||
method: form
|
||||
cookies: ["JAVA=OK"] # avoid jscheck redirect
|
||||
inputs:
|
||||
username: "{{ .Config.username }}"
|
||||
password: "{{ .Config.password }}"
|
||||
|
||||
88
src/Jackett/Definitions/nexttorrent.yml
Normal file
88
src/Jackett/Definitions/nexttorrent.yml
Normal file
@@ -0,0 +1,88 @@
|
||||
---
|
||||
site: nexttorrent
|
||||
name: NextTorrent
|
||||
language: fr-fr
|
||||
type: public
|
||||
encoding: UTF-8
|
||||
links:
|
||||
- https://www.nextorrent.org/
|
||||
|
||||
caps:
|
||||
categorymappings:
|
||||
- {id: films, cat: Movies, desc: "Movies"}
|
||||
- {id: series, cat: TV, desc: "TV"}
|
||||
|
||||
modes:
|
||||
search: [q]
|
||||
tv-search: [q, season, ep]
|
||||
|
||||
settings: []
|
||||
|
||||
download:
|
||||
selector: a[href^="/get_torrent/"]
|
||||
|
||||
search:
|
||||
path: "recherche/{{ .Query.Keywords }}"
|
||||
rows:
|
||||
selector: div.listing-torrent > table tbody tr
|
||||
fields:
|
||||
site_date:
|
||||
selector: td:nth-child(1) a
|
||||
filters:
|
||||
# date is at the end of the title, so we get it and name it site_date
|
||||
- name: regexp
|
||||
args: "(\\w+)$"
|
||||
title:
|
||||
selector: td:nth-child(1) a
|
||||
filters:
|
||||
# now we put the date at the right place according scene naming rules using .Result.site_date
|
||||
- name: replace
|
||||
args: ["FRENCH", "{{ .Result.site_date }} FRENCH"]
|
||||
- name: replace
|
||||
args: ["TRUEFRENCH", "{{ .Result.site_date }} TRUEFRENCH"]
|
||||
- name: replace
|
||||
args: ["VOSTFR", "{{ .Result.site_date }} VOSTFR"]
|
||||
# and we delete it at the end
|
||||
- name: re_replace
|
||||
args: ["(\\w+)$", ""]
|
||||
details:
|
||||
selector: td:nth-child(1) a
|
||||
attribute: href
|
||||
download:
|
||||
selector: td:nth-child(1) a
|
||||
attribute: href
|
||||
size:
|
||||
selector: td:nth-child(2)
|
||||
filters:
|
||||
- name: re_replace
|
||||
args: [ "\\.(\\d) Ko", "$1X00"]
|
||||
- name: re_replace
|
||||
args: [ " Ko", "000"]
|
||||
- name: re_replace
|
||||
args: [ "\\.(\\d) Mo", "$1X00000"]
|
||||
- name: re_replace
|
||||
args: [ " Mo", "000000"]
|
||||
- name: re_replace
|
||||
args: [ "\\.(\\d) Go", "$1X00000000"]
|
||||
- name: re_replace
|
||||
args: [ " Go", "000000000"]
|
||||
- name: re_replace
|
||||
args: [ "\\.(\\d) To", "$1X00000000000"]
|
||||
- name: re_replace
|
||||
args: [ " To", "000000000000"]
|
||||
- name: replace
|
||||
args: [ "X", "" ]
|
||||
seeders:
|
||||
text: 0
|
||||
seeders:
|
||||
selector: td:nth-child(3)
|
||||
optional: true
|
||||
leechers:
|
||||
text: 0
|
||||
leechers:
|
||||
selector: td:nth-child(4)
|
||||
optional: true
|
||||
downloadvolumefactor:
|
||||
text: "0"
|
||||
uploadvolumefactor:
|
||||
text: "1"
|
||||
@@ -79,9 +79,9 @@
|
||||
- name: split
|
||||
args: [ "=", -1 ]
|
||||
title:
|
||||
selector: td:nth-child(2) a
|
||||
selector: td:nth-child(2) a:last-of-type
|
||||
details:
|
||||
selector: td:nth-child(2) a
|
||||
selector: td:nth-child(2) a:last-of-type
|
||||
attribute: href
|
||||
download:
|
||||
selector: td:nth-child(3) a[href$=".torrent"]
|
||||
|
||||
@@ -39,24 +39,24 @@
|
||||
page: "search"
|
||||
q: "{{ .Query.Keywords}}"
|
||||
rows:
|
||||
selector: tr.tlistrow
|
||||
selector: tr.torrent-info
|
||||
fields:
|
||||
title:
|
||||
selector: td.tlistname a
|
||||
selector: td.tr-name a
|
||||
category:
|
||||
selector: td.tlisticon a
|
||||
selector: td.tr-cat div.nyaa-cat a
|
||||
attribute: href
|
||||
filters:
|
||||
- name: split
|
||||
args: [ "=", -1 ]
|
||||
details:
|
||||
selector: td.tlistname a
|
||||
selector: td.tr-name a
|
||||
attribute: href
|
||||
download:
|
||||
selector: a[title="Magnet Download"]
|
||||
selector: a[title="Magnet Link"]
|
||||
attribute: href
|
||||
size:
|
||||
selector: td.tlistsize
|
||||
selector: td.tr-size
|
||||
# seeders:
|
||||
# text: 0
|
||||
# seeders:
|
||||
|
||||
@@ -54,20 +54,20 @@
|
||||
selector: td:nth-child(2) > img
|
||||
attribute: src
|
||||
size:
|
||||
selector: td:nth-child(8)
|
||||
files:
|
||||
selector: td:nth-child(5)
|
||||
grabs:
|
||||
selector: td:nth-child(9)
|
||||
files:
|
||||
selector: td:nth-child(6)
|
||||
grabs:
|
||||
selector: td:nth-child(10)
|
||||
filters:
|
||||
- name: regexp
|
||||
args: ([\d\.]+)
|
||||
seeders:
|
||||
selector: td:nth-child(10)
|
||||
leechers:
|
||||
selector: td:nth-child(11)
|
||||
leechers:
|
||||
selector: td:nth-child(12)
|
||||
date:
|
||||
selector: td:nth-child(7)
|
||||
selector: td:nth-child(8)
|
||||
downloadvolumefactor:
|
||||
case:
|
||||
"a.info > b:contains(\"Free\")": "0"
|
||||
|
||||
@@ -50,9 +50,9 @@
|
||||
- name: dateparse
|
||||
args: "01-02-2006"
|
||||
download:
|
||||
selector: td > a[href^="/file"]
|
||||
selector: td > a[href^="magnet"]
|
||||
attribute: href
|
||||
downloadvolumefactor:
|
||||
text: "0"
|
||||
uploadvolumefactor:
|
||||
text: "1"
|
||||
text: "1"
|
||||
|
||||
104
src/Jackett/Definitions/t411v2.yml
Normal file
104
src/Jackett/Definitions/t411v2.yml
Normal file
@@ -0,0 +1,104 @@
|
||||
---
|
||||
site: t411v2
|
||||
name: t411 v2
|
||||
language: fr-fr
|
||||
type: semi-private
|
||||
encoding: UTF-8
|
||||
links:
|
||||
- https://t411.si
|
||||
caps:
|
||||
categorymappings:
|
||||
- {id: 1, cat: Movies}
|
||||
- {id: 2, cat: TV}
|
||||
modes:
|
||||
search: [q]
|
||||
tv-search: [q, season, ep]
|
||||
settings: []
|
||||
|
||||
download:
|
||||
selector: a[href^="/telecharger-torrent/"]
|
||||
|
||||
search:
|
||||
path: /torrents/search/?search={{ .Keywords}}
|
||||
rows:
|
||||
selector: div.isItem
|
||||
fields:
|
||||
site_date:
|
||||
selector: div.gname a
|
||||
filters:
|
||||
# date is at the end of the title, so we get it and name it
|
||||
# site_date
|
||||
- name: regexp
|
||||
args: "(\\w+)$"
|
||||
title:
|
||||
selector: div.gname a
|
||||
filters:
|
||||
# now we put the date at the right place according scene
|
||||
# naming rules using .Result.site_date
|
||||
- name: replace
|
||||
args: ["FRENCH", "{{ .Result.site_date }} FRENCH"]
|
||||
- name: replace
|
||||
args: ["TRUEFRENCH", "{{ .Result.site_date }} TRUEFRENCH"]
|
||||
- name: replace
|
||||
args: ["VOSTFR", "{{ .Result.site_date }} VOSTFR"]
|
||||
# and we delete it at the end
|
||||
- name: re_replace
|
||||
args: ["(\\w+)$", ""]
|
||||
details:
|
||||
selector: div.gname a
|
||||
attribute: href
|
||||
download:
|
||||
selector: div.gname a
|
||||
attribute: href
|
||||
size:
|
||||
selector: div.gsmall:nth-child(5) span
|
||||
filters:
|
||||
- name: re_replace
|
||||
args: [ "\\.(\\w+) K", "$1X00"]
|
||||
- name: re_replace
|
||||
args: [ " K", "000"]
|
||||
- name: re_replace
|
||||
args: [ "\\.(\\w+) M", "$1X0000"]
|
||||
- name: re_replace
|
||||
args: [ " M", "000000"]
|
||||
- name: re_replace
|
||||
args: [ "\\.(\\w+) G", "$1X0000000"]
|
||||
- name: re_replace
|
||||
args: [ " G", "000000000"]
|
||||
seeders:
|
||||
text: 0
|
||||
seeders:
|
||||
selector: div.gsmall:nth-child(6)
|
||||
optional: true
|
||||
leechers:
|
||||
text: 0
|
||||
leechers:
|
||||
selector: div.gsmall:nth-child(7)
|
||||
optional: true
|
||||
downloadvolumefactor:
|
||||
text: "0"
|
||||
uploadvolumefactor:
|
||||
text: "1"
|
||||
date:
|
||||
selector: div.gsmall:nth-child(4) span
|
||||
filters:
|
||||
- name: replace
|
||||
args: [ " jours", " days"]
|
||||
- name: replace
|
||||
args: [ " jour", " day"]
|
||||
- name: replace
|
||||
args: [ " heures", " hours"]
|
||||
- name: replace
|
||||
args: [ " heure", " hour"]
|
||||
- name: replace
|
||||
args: [ " semaines", " weeks"]
|
||||
- name: replace
|
||||
args: [ " semaine", " week"]
|
||||
- name: replace
|
||||
args: [ " mois", " month"]
|
||||
- name: replace
|
||||
args: [ " ans", " years"]
|
||||
- name: replace
|
||||
args: [ " an", " year"]
|
||||
- name: append
|
||||
args: " ago"
|
||||
@@ -6,7 +6,7 @@
|
||||
type: private
|
||||
encoding: UTF-8
|
||||
links:
|
||||
- http://thegeeks.click/
|
||||
- https://thegeeks.click/
|
||||
|
||||
caps:
|
||||
categorymappings:
|
||||
|
||||
@@ -73,7 +73,12 @@
|
||||
settings: []
|
||||
|
||||
search:
|
||||
path: "{{if .Query.Keywords}}/search/{{ .Query.Keywords}}/0/99/{{range .Categories }}{{.}}{{end}}{{else}}/recent{{end}}"
|
||||
path: "{{if .Query.Keywords}}/search/{{ .Keywords}}/0/99/{{range .Categories }}{{.}}{{end}}{{else}}/recent{{end}}"
|
||||
keywordsfilters:
|
||||
# currently, the only uploader for General Hospital puts a space between season and episode
|
||||
# this filter searches both formats, so "General Hospital S01E02" becomes "General Hospital S01E02 | (S01 E02)"
|
||||
- name: re_replace
|
||||
args: ["General Hospital S(\\d{2,3})E(\\d{2,3})", "$0 | \\(S$1 E$2\\)"]
|
||||
rows:
|
||||
selector: "#searchResult tbody tr:has(td.vertTh)"
|
||||
fields:
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
type: public
|
||||
encoding: UTF-8
|
||||
links:
|
||||
- http://www.torrent9.biz/
|
||||
- http://www.torrent9.cc/
|
||||
|
||||
caps:
|
||||
categorymappings:
|
||||
@@ -28,8 +28,25 @@
|
||||
rows:
|
||||
selector: div.table-responsive > table tbody tr
|
||||
fields:
|
||||
site_date:
|
||||
selector: td:nth-child(1) a
|
||||
filters:
|
||||
# date is at the end of the title, so we get it and name it site_date
|
||||
- name: regexp
|
||||
args: "(\\w+)$"
|
||||
title:
|
||||
selector: td:nth-child(1) a
|
||||
filters:
|
||||
# now we put the date at the right place according scene naming rules using .Result.site_date
|
||||
- name: replace
|
||||
args: ["FRENCH", "{{ .Result.site_date }} FRENCH"]
|
||||
- name: replace
|
||||
args: ["TRUEFRENCH", "{{ .Result.site_date }} TRUEFRENCH"]
|
||||
- name: replace
|
||||
args: ["VOSTFR", "{{ .Result.site_date }} VOSTFR"]
|
||||
# and we delete it at the end
|
||||
- name: re_replace
|
||||
args: ["(\\w+)$", ""]
|
||||
details:
|
||||
selector: td:nth-child(1) a
|
||||
attribute: href
|
||||
@@ -75,4 +92,4 @@
|
||||
downloadvolumefactor:
|
||||
text: "0"
|
||||
uploadvolumefactor:
|
||||
text: "1"
|
||||
text: "1"
|
||||
|
||||
61
src/Jackett/Definitions/torrentdownloads.yml
Normal file
61
src/Jackett/Definitions/torrentdownloads.yml
Normal file
@@ -0,0 +1,61 @@
|
||||
---
|
||||
site: torrentdownloads
|
||||
name: Torrent Downloads
|
||||
language: en-us
|
||||
type: public
|
||||
encoding: UTF-8
|
||||
links:
|
||||
- https://www.torrentdownloads.me/
|
||||
|
||||
caps:
|
||||
categorymappings:
|
||||
- {id: 8, cat: TV, desc: "TV Shows"}
|
||||
- {id: 4, cat: Movies, desc: "Movies"}
|
||||
- {id: 5, cat: Audio, desc: "Music"}
|
||||
- {id: 3, cat: PC/Games, desc: "Games"}
|
||||
- {id: 7, cat: PC, desc: "Software"}
|
||||
- {id: 1, cat: TV/Anime, desc: "Anime"}
|
||||
- {id: 2, cat: Books, desc: "Books"}
|
||||
- {id: 9, cat: Other, desc: "Other"}
|
||||
|
||||
modes:
|
||||
search: [q]
|
||||
|
||||
settings: []
|
||||
|
||||
search:
|
||||
path: "{{if .Keywords}}/search/{{else}}/today/{{end}}"
|
||||
inputs:
|
||||
$raw: "new=1&{{range .Categories}}s_cat={{.}}&{{end}}"
|
||||
search: "{{ .Query.Keywords }}"
|
||||
rows:
|
||||
selector: div.inner_container > div:has(p:has(a[href^="/torrent/"]))
|
||||
fields:
|
||||
title:
|
||||
selector: p:nth-child(1) > a
|
||||
details:
|
||||
selector: p:nth-child(1) > a[href^="/torrent/"]
|
||||
attribute: href
|
||||
download:
|
||||
selector: p:nth-child(1) > a[href^="/torrent/"]
|
||||
attribute: href
|
||||
filters:
|
||||
- name: replace
|
||||
args: ["/torrent/", "/download/"]
|
||||
size:
|
||||
selector: span:nth-child(5)
|
||||
seeders:
|
||||
selector: span:nth-child(4)
|
||||
leechers:
|
||||
selector: span:nth-child(3)
|
||||
category:
|
||||
selector: img[src^="/templates/new/images/icons/menu_icon"]
|
||||
attribute: src
|
||||
filters:
|
||||
- name: regexp
|
||||
args: ([\d,]+)
|
||||
- name: strdump
|
||||
downloadvolumefactor:
|
||||
text: "1"
|
||||
uploadvolumefactor:
|
||||
text: "1"
|
||||
@@ -93,7 +93,7 @@
|
||||
searchin: "title"
|
||||
|
||||
rows:
|
||||
selector: table.browsewidth100 > tbody > tr:has(a[href^="download.php?torrent="])
|
||||
selector: table > tbody > tr:has(a[href^="download.php?torrent="])
|
||||
filters:
|
||||
- name: andmatch
|
||||
fields:
|
||||
|
||||
@@ -86,23 +86,23 @@
|
||||
selector: a[href^="dwn.php"]
|
||||
attribute: href
|
||||
size:
|
||||
selector: td:nth-child(6)
|
||||
selector: td:nth-child(7)
|
||||
date:
|
||||
selector: td:nth-child(5)
|
||||
selector: td:nth-child(6)
|
||||
filters:
|
||||
- name: append
|
||||
args: " +00:00"
|
||||
- name: dateparse
|
||||
args: "02-01-200615:04:05 -07:00"
|
||||
grabs:
|
||||
selector: td:nth-child(7)
|
||||
selector: td:nth-child(8)
|
||||
filters:
|
||||
- name: regexp
|
||||
args: ([\d\.]+)
|
||||
seeders:
|
||||
selector: td:nth-child(8)
|
||||
leechers:
|
||||
selector: td:nth-child(9)
|
||||
leechers:
|
||||
selector: td:nth-child(10)
|
||||
banner:
|
||||
selector: a[onmouseover][href^="details.php?id="]
|
||||
attribute: onmouseover
|
||||
|
||||
146
src/Jackett/Definitions/yggtorrent.yml
Executable file
146
src/Jackett/Definitions/yggtorrent.yml
Executable file
@@ -0,0 +1,146 @@
|
||||
---
|
||||
site: yggtorrent
|
||||
name: YGGtorrent
|
||||
language: fr-fr
|
||||
type: semi-private
|
||||
encoding: UTF-8
|
||||
links:
|
||||
- https://yggtorrent.com
|
||||
|
||||
caps:
|
||||
categorymappings:
|
||||
# Film/Video for search results
|
||||
- {id: 2145, cat: Other, desc: "Movies & TV"}
|
||||
- {id: 2178, cat: Movies, desc: "Anim Movies"}
|
||||
- {id: 2179, cat: TV/Anime, desc: "Anim TV"}
|
||||
- {id: 2180, cat: Other, desc: "Concerts"}
|
||||
- {id: 2181, cat: TV/Documentary, desc: "Documentary"}
|
||||
- {id: 2182, cat: TV, desc: "TV Shows"}
|
||||
- {id: 2183, cat: Movies, desc: "Movies"}
|
||||
- {id: 2184, cat: TV, desc: "TV"}
|
||||
- {id: 2185, cat: TV/Other, desc: "Shows"}
|
||||
- {id: 2186, cat: TV/Sport, desc: "Sport"}
|
||||
- {id: 2187, cat: TV/Other, desc: "Clips"}
|
||||
# Film/Video for blank search
|
||||
- {id: "Tous les torrents", cat: Other, desc: "Movies & TV"}
|
||||
- {id: "Animation", cat: Movies, desc: "Anim Movies"}
|
||||
- {id: "Animation Série", cat: TV/Anime, desc: "Anim TV"}
|
||||
- {id: "Concert", cat: Other, desc: "Concerts"}
|
||||
- {id: "Documentaire", cat: TV/Documentary, desc: "Documentary"}
|
||||
- {id: "Emission TV", cat: TV, desc: "TV Shows"}
|
||||
- {id: "Film", cat: Movies, desc: "Movies"}
|
||||
- {id: "Série TV", cat: TV, desc: "TV"}
|
||||
- {id: "Spectacle", cat: TV/Other, desc: "Shows"}
|
||||
- {id: "Sport", cat: TV/Sport, desc: "Sport"}
|
||||
- {id: "Vidéo-clips", cat: TV/Other, desc: "Clips"}
|
||||
modes:
|
||||
search: [q]
|
||||
#tv-search: [q, season, ep]
|
||||
settings:
|
||||
- name: username
|
||||
type: text
|
||||
label: Username (email or username)
|
||||
- name: password
|
||||
type: password
|
||||
label: Password
|
||||
login:
|
||||
path: "/user/login"
|
||||
method: post
|
||||
inputs:
|
||||
id: "{{ .Config.username }}"
|
||||
pass: "{{ .Config.password }}"
|
||||
submit: ""
|
||||
headers:
|
||||
"[:authority]": "yggtorrent.com"
|
||||
"[:method]": "post"
|
||||
"[:path]": "/"
|
||||
"[:scheme]": "https"
|
||||
error:
|
||||
- selector: "body > div.page-content > div > div.col-md-10 > div > div > div > div > div.content-box-large.box-with-header > form > center > table > tbody > tr:nth-child(3) > td:nth-child(2) > button.text:contains('Se connecter')"
|
||||
test:
|
||||
path: "/"
|
||||
selector: "a[href=\"https://yggtorrent.com/user/logout\"]"
|
||||
search:
|
||||
paths:
|
||||
- path: "{{if .Keywords}}/engine/search?q={{ .Keywords}}{{else}}/torrents/2145-filmvideo{{end}}"
|
||||
- path: "{{if .Keywords}}/engine/search?q={{ .Keywords}}&page=15{{else}}/torrents/2145-filmvideo?page=25{{end}}"
|
||||
- path: "{{if .Keywords}}/engine/search?q={{ .Keywords}}&page=30{{else}}/torrents/2145-filmvideo?page=50{{end}}"
|
||||
rows:
|
||||
selector: "table.table.table-striped > tbody > tr"
|
||||
fields:
|
||||
site_date:
|
||||
selector: "td:nth-child(3)"
|
||||
filters:
|
||||
- name: replace
|
||||
args: ["il y a ", ""]
|
||||
- name: replace
|
||||
args: [ " jours", " days"]
|
||||
- name: replace
|
||||
args: [ " jour", " day"]
|
||||
- name: replace
|
||||
args: [ " heures", " hours"]
|
||||
- name: replace
|
||||
args: [ " heure", " hour"]
|
||||
- name: replace
|
||||
args: [ " semaines", " weeks"]
|
||||
- name: replace
|
||||
args: [ " semaine", " week"]
|
||||
- name: replace
|
||||
args: [ " mois", " month"]
|
||||
- name: replace
|
||||
args: [ " ans", " years"]
|
||||
- name: replace
|
||||
args: [ " an", " year"]
|
||||
- name: append
|
||||
args: " ago"
|
||||
title:
|
||||
selector: "a.torrent-name"
|
||||
details:
|
||||
selector: "a.torrent-name"
|
||||
attribute: href
|
||||
category:
|
||||
selector: "td:nth-child(1) > span > i:last-child > a"
|
||||
comments:
|
||||
optional: true
|
||||
selector: "td:nth-child(1) > a[href$=\"#comments\"]"
|
||||
attribute: href
|
||||
download:
|
||||
selector: "td:nth-child(1) > a[href^=\"https://yggtorrent.com/engine/download_torrent?id=\"]"
|
||||
attribute: href
|
||||
size:
|
||||
selector: "td:nth-child(4)"
|
||||
filters:
|
||||
- name: re_replace
|
||||
args: [ "\\.(\\d) KB", "$1X00"]
|
||||
- name: re_replace
|
||||
args: [ " KB", "000"]
|
||||
- name: re_replace
|
||||
args: [ "\\.(\\d) MB", "$1X00000"]
|
||||
- name: re_replace
|
||||
args: [ " MB", "000000"]
|
||||
- name: re_replace
|
||||
args: [ "\\.(\\d) GB", "$1X00000000"]
|
||||
- name: re_replace
|
||||
args: [ " GB", "000000000"]
|
||||
- name: re_replace
|
||||
args: [ "\\.(\\d) TB", "$1X00000000000"]
|
||||
- name: re_replace
|
||||
args: [ " TB", "000000000000"]
|
||||
- name: replace
|
||||
args: [ "X", "" ]
|
||||
seeders:
|
||||
text: 0
|
||||
seeders:
|
||||
selector: "td:nth-child(5)"
|
||||
optional: true
|
||||
leechers:
|
||||
text: 0
|
||||
leechers:
|
||||
selector: "td:nth-child(6)"
|
||||
optional: true
|
||||
date:
|
||||
text: "{{ .Result.site_date }}"
|
||||
downloadvolumefactor:
|
||||
text: "1"
|
||||
uploadvolumefactor:
|
||||
text: "1"
|
||||
87
src/Jackett/Definitions/zamundanet.yml
Normal file
87
src/Jackett/Definitions/zamundanet.yml
Normal file
@@ -0,0 +1,87 @@
|
||||
---
|
||||
site: zamundanet
|
||||
name: Zamunda.net
|
||||
language: bg-bg
|
||||
type: private
|
||||
encoding: windows-1251
|
||||
links:
|
||||
- http://zamunda.net/
|
||||
|
||||
caps:
|
||||
categories:
|
||||
7: TV/SD
|
||||
33: TV/HD
|
||||
25: TV/Other
|
||||
5: Movies/HD
|
||||
19: Movies/SD
|
||||
46: Movies/3D
|
||||
42: Movies/BluRay
|
||||
20: Movies/DVD
|
||||
9: XXX
|
||||
49: XXX/Other
|
||||
|
||||
modes:
|
||||
search: [q]
|
||||
tv-search: [q, season, ep]
|
||||
movie-search: [q]
|
||||
|
||||
login:
|
||||
path: /takelogin.php
|
||||
method: post
|
||||
inputs:
|
||||
username: "{{ .Config.username }}"
|
||||
password: "{{ .Config.password }}"
|
||||
error:
|
||||
- selector: td.embedded:has(h2:contains("failed"))
|
||||
test:
|
||||
path: /bananas
|
||||
|
||||
search:
|
||||
path: /bananas
|
||||
inputs:
|
||||
$raw: "{{range .Categories}}c{{.}}=1&{{end}}"
|
||||
search: "{{ .Query.Keywords }}"
|
||||
incldead: 1
|
||||
rows:
|
||||
selector: .responsetop > tbody > tr:has(td.td_newborder)
|
||||
fields:
|
||||
title:
|
||||
selector: td:nth-child(2) > a:nth-child(1)
|
||||
details:
|
||||
selector: td:nth-child(2) > a:nth-child(1)
|
||||
attribute: href
|
||||
category:
|
||||
selector: td:nth-child(1) > a
|
||||
attribute: href
|
||||
filters:
|
||||
- name: querystring
|
||||
args: cat
|
||||
download:
|
||||
selector: a:has(i.fa-download)
|
||||
attribute: href
|
||||
grabs:
|
||||
selector: td:nth-child(7)
|
||||
filters:
|
||||
- name: regexp
|
||||
args: (\d+)
|
||||
size:
|
||||
selector: td:nth-child(6)
|
||||
date:
|
||||
selector: td:nth-child(5)
|
||||
filters:
|
||||
- name: regexp
|
||||
args: ([12]\d{3}-(0[1-9]|1[0-2])-(0[1-9]|[12]\d|3[01]))
|
||||
seeders:
|
||||
selector: td:nth-child(8)
|
||||
leechers:
|
||||
selector: td:nth-child(9)
|
||||
banner:
|
||||
selector: td:nth-child(2) > a:nth-child(1)
|
||||
attribute: onmouseover
|
||||
filters:
|
||||
- name: regexp
|
||||
args: src=\\'([^\s\\]+)
|
||||
downloadvolumefactor:
|
||||
text: "0"
|
||||
uploadvolumefactor:
|
||||
text: "1"
|
||||
93
src/Jackett/Definitions/zelkaorg.yml
Normal file
93
src/Jackett/Definitions/zelkaorg.yml
Normal file
@@ -0,0 +1,93 @@
|
||||
---
|
||||
site: zelkaorg
|
||||
name: Zelka.org
|
||||
language: bg-bg
|
||||
type: private
|
||||
encoding: windows-1251
|
||||
links:
|
||||
- http://zelka.org/
|
||||
- http://zamunda.se/
|
||||
|
||||
caps:
|
||||
categories:
|
||||
7: TV/SD
|
||||
33: TV/HD
|
||||
25: TV/Other
|
||||
5: Movies/HD
|
||||
53: Movies/HD
|
||||
35: Movies/WEBDL
|
||||
19: Movies/SD
|
||||
46: Movies/3D
|
||||
42: Movies/BluRay
|
||||
20: Movies/DVD
|
||||
9: XXX
|
||||
49: XXX/Other
|
||||
|
||||
modes:
|
||||
search: [q]
|
||||
tv-search: [q, season, ep]
|
||||
movie-search: [q]
|
||||
|
||||
login:
|
||||
path: /takelogin.php
|
||||
method: post
|
||||
inputs:
|
||||
username: "{{ .Config.username }}"
|
||||
password: "{{ .Config.password }}"
|
||||
error:
|
||||
- selector: td.embedded:has(h2:contains("failed"))
|
||||
test:
|
||||
path: /browse.php
|
||||
|
||||
search:
|
||||
path: /browse.php
|
||||
inputs:
|
||||
$raw: "{{range .Categories}}cat{{.}}=1&{{end}}"
|
||||
search: "{{ .Query.Keywords }}"
|
||||
incldead: 1
|
||||
rows:
|
||||
selector: .test > tbody > tr:has(a[href^="browse.php"])
|
||||
fields:
|
||||
title:
|
||||
selector: td:nth-child(2) > a:nth-child(1)
|
||||
details:
|
||||
selector: td:nth-child(2) > a:nth-child(1)
|
||||
attribute: href
|
||||
category:
|
||||
selector: td:nth-child(1) > a
|
||||
attribute: href
|
||||
filters:
|
||||
- name: querystring
|
||||
args: cat
|
||||
download:
|
||||
selector: a:has(img[src^="http://img.zamunda.se/pic/download.gif"])
|
||||
attribute: href
|
||||
magnet:
|
||||
selector: a:has(img[src^="http://img.zamunda.se/pic/magnet-icon-12w-12h.gif"])
|
||||
attribute: href
|
||||
grabs:
|
||||
selector: td:nth-child(7)
|
||||
filters:
|
||||
- name: regexp
|
||||
args: (\d+)
|
||||
size:
|
||||
selector: td:nth-child(6)
|
||||
date:
|
||||
selector: td:nth-child(5)
|
||||
filters:
|
||||
- name: regexp
|
||||
args: ([12]\d{3}-(0[1-9]|1[0-2])-(0[1-9]|[12]\d|3[01]))
|
||||
seeders:
|
||||
selector: td:nth-child(8)
|
||||
leechers:
|
||||
selector: td:nth-child(9)
|
||||
banner:
|
||||
selector: td:nth-child(2) > a:nth-child(1)
|
||||
attribute: onmouseover
|
||||
filters:
|
||||
- name: regexp
|
||||
args: src=([^\s]+)
|
||||
downloadvolumefactor:
|
||||
text: "0"
|
||||
uploadvolumefactor:
|
||||
text: "1"
|
||||
88
src/Jackett/Definitions/zetorrents.yml
Normal file
88
src/Jackett/Definitions/zetorrents.yml
Normal file
@@ -0,0 +1,88 @@
|
||||
---
|
||||
site: zetorrents
|
||||
name: zetorrents
|
||||
language: fr-fr
|
||||
type: public
|
||||
encoding: UTF-8
|
||||
links:
|
||||
- http://www.zetorrents.cc/
|
||||
|
||||
caps:
|
||||
categorymappings:
|
||||
- {id: films, cat: Movies, desc: "Movies"}
|
||||
- {id: series, cat: TV, desc: "TV"}
|
||||
|
||||
modes:
|
||||
search: [q]
|
||||
tv-search: [q, season, ep]
|
||||
|
||||
settings: []
|
||||
|
||||
download:
|
||||
selector: a[href^="/get_torrent/"]
|
||||
|
||||
search:
|
||||
path: "/recherche/{{ .Query.Keywords }}"
|
||||
rows:
|
||||
selector: div.content-list-torrent > table tbody tr
|
||||
fields:
|
||||
site_date:
|
||||
selector: td:nth-child(1) a
|
||||
filters:
|
||||
# date is at the end of the title, so we get it and name it site_date
|
||||
- name: regexp
|
||||
args: "(\\w+)$"
|
||||
title:
|
||||
selector: td:nth-child(1) a
|
||||
filters:
|
||||
# now we put the date at the right place according scene naming rules using .Result.site_date
|
||||
- name: replace
|
||||
args: ["FRENCH", "{{ .Result.site_date }} FRENCH"]
|
||||
- name: replace
|
||||
args: ["TRUEFRENCH", "{{ .Result.site_date }} TRUEFRENCH"]
|
||||
- name: replace
|
||||
args: ["VOSTFR", "{{ .Result.site_date }} VOSTFR"]
|
||||
# and we delete it at the end
|
||||
- name: re_replace
|
||||
args: ["(\\w+)$", ""]
|
||||
details:
|
||||
selector: td:nth-child(1) a
|
||||
attribute: href
|
||||
download:
|
||||
selector: td:nth-child(1) a
|
||||
attribute: href
|
||||
size:
|
||||
selector: td:nth-child(2)
|
||||
filters:
|
||||
- name: re_replace
|
||||
args: [ "\\.(\\d) Ko", "$1X00"]
|
||||
- name: re_replace
|
||||
args: [ " Ko", "000"]
|
||||
- name: re_replace
|
||||
args: [ "\\.(\\d) Mo", "$1X00000"]
|
||||
- name: re_replace
|
||||
args: [ " Mo", "000000"]
|
||||
- name: re_replace
|
||||
args: [ "\\.(\\d) Go", "$1X00000000"]
|
||||
- name: re_replace
|
||||
args: [ " Go", "000000000"]
|
||||
- name: re_replace
|
||||
args: [ "\\.(\\d) To", "$1X00000000000"]
|
||||
- name: re_replace
|
||||
args: [ " To", "000000000000"]
|
||||
- name: replace
|
||||
args: [ "X", "" ]
|
||||
seeders:
|
||||
text: 0
|
||||
seeders:
|
||||
selector: td:nth-child(3)
|
||||
optional: true
|
||||
leechers:
|
||||
text: 0
|
||||
leechers:
|
||||
selector: td:nth-child(4)
|
||||
optional: true
|
||||
downloadvolumefactor:
|
||||
text: "0"
|
||||
uploadvolumefactor:
|
||||
text: "1"
|
||||
@@ -10,6 +10,8 @@ using System.IO;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using System.Web.Http.Dependencies;
|
||||
using Autofac.Integration.WebApi;
|
||||
|
||||
namespace Jackett
|
||||
{
|
||||
@@ -37,9 +39,9 @@ namespace Jackett
|
||||
|
||||
}
|
||||
|
||||
public static IContainer GetContainer()
|
||||
public static IDependencyResolver DependencyResolver()
|
||||
{
|
||||
return container;
|
||||
return new AutofacWebApiDependencyResolver(container);
|
||||
}
|
||||
|
||||
public static bool IsWindows
|
||||
|
||||
@@ -18,7 +18,7 @@ using System.Web;
|
||||
|
||||
namespace Jackett.Indexers
|
||||
{
|
||||
public class SevenTor : BaseIndexer, IIndexer
|
||||
public class SevenTor : BaseWebIndexer
|
||||
{
|
||||
string LoginUrl { get { return SiteLink + "ucp.php?mode=login"; } }
|
||||
string SearchUrl { get { return SiteLink + "search.php"; } }
|
||||
@@ -29,12 +29,12 @@ namespace Jackett.Indexers
|
||||
set { base.configData = value; }
|
||||
}
|
||||
|
||||
public SevenTor(IIndexerManagerService i, IWebClient wc, Logger l, IProtectionService ps)
|
||||
public SevenTor(IIndexerConfigurationService configService, IWebClient wc, Logger l, IProtectionService ps)
|
||||
: base(name: "7tor",
|
||||
description: null,
|
||||
link: "https://7tor.org/",
|
||||
caps: TorznabUtil.CreateDefaultTorznabTVCaps(),
|
||||
manager: i,
|
||||
configService: configService,
|
||||
client: wc,
|
||||
logger: l,
|
||||
p: ps,
|
||||
@@ -1584,7 +1584,7 @@ namespace Jackett.Indexers
|
||||
AddCategoryMapping(1569, TorznabCatType.Other, " Amateur videos");
|
||||
}
|
||||
|
||||
public async Task<IndexerConfigurationStatus> ApplyConfiguration(JToken configJson)
|
||||
public override async Task<IndexerConfigurationStatus> ApplyConfiguration(JToken configJson)
|
||||
{
|
||||
LoadValuesFromJson(configJson);
|
||||
|
||||
@@ -1606,7 +1606,7 @@ namespace Jackett.Indexers
|
||||
return IndexerConfigurationStatus.RequiresTesting;
|
||||
}
|
||||
|
||||
public async Task<IEnumerable<ReleaseInfo>> PerformQuery(TorznabQuery query)
|
||||
protected override async Task<IEnumerable<ReleaseInfo>> PerformQuery(TorznabQuery query)
|
||||
{
|
||||
var releases = new List<ReleaseInfo>();
|
||||
var searchString = query.GetQueryString();
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.Specialized;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using System.Text;
|
||||
@@ -22,7 +23,7 @@ namespace Jackett.Indexers
|
||||
/// <summary>
|
||||
/// Provider for Abnormal Private French Tracker
|
||||
/// </summary>
|
||||
public class Abnormal : BaseIndexer, IIndexer
|
||||
public class Abnormal : BaseCachingWebIndexer
|
||||
{
|
||||
private string LoginUrl { get { return SiteLink + "login.php"; } }
|
||||
private string SearchUrl { get { return SiteLink + "torrents.php"; } }
|
||||
@@ -32,7 +33,7 @@ namespace Jackett.Indexers
|
||||
private bool Latency { get { return ConfigData.Latency.Value; } }
|
||||
private bool DevMode { get { return ConfigData.DevMode.Value; } }
|
||||
private bool CacheMode { get { return ConfigData.HardDriveCache.Value; } }
|
||||
private string directory { get { return System.IO.Path.GetTempPath() + "Jackett\\" + MethodBase.GetCurrentMethod().DeclaringType.Name + "\\"; } }
|
||||
private static string Directory => Path.Combine(Path.GetTempPath(), Assembly.GetExecutingAssembly().GetName().Name.ToLower(), MethodBase.GetCurrentMethod().DeclaringType?.Name.ToLower());
|
||||
|
||||
private Dictionary<string, string> emulatedBrowserHeaders = new Dictionary<string, string>();
|
||||
private CQ fDom = null;
|
||||
@@ -43,13 +44,13 @@ namespace Jackett.Indexers
|
||||
set { base.configData = value; }
|
||||
}
|
||||
|
||||
public Abnormal(IIndexerManagerService i, IWebClient w, Logger l, IProtectionService ps)
|
||||
public Abnormal(IIndexerConfigurationService configService, IWebClient w, Logger l, IProtectionService ps)
|
||||
: base(
|
||||
name: "Abnormal",
|
||||
description: "General French Private Tracker",
|
||||
link: "https://abnormal.ws/",
|
||||
caps: new TorznabCapabilities(),
|
||||
manager: i,
|
||||
configService: configService,
|
||||
client: w,
|
||||
logger: l,
|
||||
p: ps,
|
||||
@@ -104,7 +105,7 @@ namespace Jackett.Indexers
|
||||
/// </summary>
|
||||
/// <param name="configJson">Our params in Json</param>
|
||||
/// <returns>Configuration state</returns>
|
||||
public async Task<IndexerConfigurationStatus> ApplyConfiguration(JToken configJson)
|
||||
public override async Task<IndexerConfigurationStatus> ApplyConfiguration(JToken configJson)
|
||||
{
|
||||
// Retrieve config values set by Jackett's user
|
||||
LoadValuesFromJson(configJson);
|
||||
@@ -117,7 +118,8 @@ namespace Jackett.Indexers
|
||||
// emulatedBrowserHeaders.Add("Accept-Encoding", "gzip, deflate");
|
||||
|
||||
// If we want to simulate a browser
|
||||
if (ConfigData.Browser.Value) {
|
||||
if (ConfigData.Browser.Value)
|
||||
{
|
||||
|
||||
// Clean headers
|
||||
emulatedBrowserHeaders.Clear();
|
||||
@@ -149,7 +151,8 @@ namespace Jackett.Indexers
|
||||
};
|
||||
|
||||
// Do the login
|
||||
var request = new Utils.Clients.WebRequest(){
|
||||
var request = new Utils.Clients.WebRequest()
|
||||
{
|
||||
PostData = pairs,
|
||||
Referer = LoginUrl,
|
||||
Type = RequestType.POST,
|
||||
@@ -187,7 +190,7 @@ namespace Jackett.Indexers
|
||||
/// </summary>
|
||||
/// <param name="query">Query</param>
|
||||
/// <returns>Releases</returns>
|
||||
public async Task<IEnumerable<ReleaseInfo>> PerformQuery(TorznabQuery query)
|
||||
protected override async Task<IEnumerable<ReleaseInfo>> PerformQuery(TorznabQuery query)
|
||||
{
|
||||
var releases = new List<ReleaseInfo>();
|
||||
var torrentRowList = new List<CQ>();
|
||||
@@ -197,7 +200,7 @@ namespace Jackett.Indexers
|
||||
int pageLinkCount = 0;
|
||||
|
||||
// Check cache first so we don't query the server (if search term used or not in dev mode)
|
||||
if(!DevMode && !string.IsNullOrEmpty(searchTerm))
|
||||
if (!DevMode && !string.IsNullOrEmpty(searchTerm))
|
||||
{
|
||||
lock (cache)
|
||||
{
|
||||
@@ -215,8 +218,7 @@ namespace Jackett.Indexers
|
||||
var request = buildQuery(searchTerm, query, searchUrl);
|
||||
|
||||
// Getting results & Store content
|
||||
WebClientStringResult results = await queryExec(request);
|
||||
fDom = results.Content;
|
||||
fDom = await queryExec(request);
|
||||
|
||||
try
|
||||
{
|
||||
@@ -230,14 +232,16 @@ namespace Jackett.Indexers
|
||||
Boolean pagination = (fDom[".linkbox > a"].Length != 0);
|
||||
|
||||
// If pagination available
|
||||
if (pagination) {
|
||||
if (pagination)
|
||||
{
|
||||
// Calculate numbers of pages available for this search query (Based on number results and number of torrents on first page)
|
||||
pageLinkCount = ParseUtil.CoerceInt(Regex.Match(fDom[".linkbox > a"].Last().Attr("href").ToString(), @"\d+").Value);
|
||||
|
||||
// Calculate average number of results (based on torrents rows lenght on first page)
|
||||
nbResults = firstPageRows.Count() * pageLinkCount;
|
||||
}
|
||||
else {
|
||||
else
|
||||
{
|
||||
// Check if we have a minimum of one result
|
||||
if (firstPageRows.Length >= 1)
|
||||
{
|
||||
@@ -270,10 +274,7 @@ namespace Jackett.Indexers
|
||||
var pageRequest = buildQuery(searchTerm, query, searchUrl, i);
|
||||
|
||||
// Getting results & Store content
|
||||
WebClientStringResult pageResults = await queryExec(pageRequest);
|
||||
|
||||
// Assign response
|
||||
fDom = pageResults.Content;
|
||||
fDom = await queryExec(pageRequest);
|
||||
|
||||
// Process page results
|
||||
var additionalPageRows = findTorrentRows();
|
||||
@@ -282,20 +283,6 @@ namespace Jackett.Indexers
|
||||
torrentRowList.AddRange(additionalPageRows.Select(fRow => fRow.Cq()));
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// No search term, maybe testing... so registring autkey and torrentpass for future uses
|
||||
string infosData = firstPageRows.First().Find("td:eq(3) > a").Attr("href");
|
||||
IList<string> infosList = infosData.Split('&').Select(s => s.Trim()).Where(s => s != String.Empty).ToList();
|
||||
IList<string> infosTracker = infosList.Select(s => s.Split(new[] { '=' }, 2)[1].Trim()).ToList();
|
||||
|
||||
output("\nStoring Authkey for future uses...");
|
||||
ConfigData.AuthKey.Value = infosTracker[2];
|
||||
|
||||
output("\nStoring TorrentPass for future uses...");
|
||||
ConfigData.TorrentPass.Value = infosTracker[3];
|
||||
|
||||
}
|
||||
|
||||
// Loop on results
|
||||
foreach (CQ tRow in torrentRowList)
|
||||
@@ -307,12 +294,13 @@ namespace Jackett.Indexers
|
||||
output("ID: " + id);
|
||||
|
||||
// Release Name
|
||||
string name = tRow.Find("td:eq(1) > a").Text().ToString();
|
||||
string name = tRow.Find("td:eq(1) > a").Text();
|
||||
output("Release: " + name);
|
||||
|
||||
// Category
|
||||
string categoryID = tRow.Find("td:eq(0) > a").Attr("href").Replace("torrents.php?cat[]=", String.Empty);
|
||||
output("Category: " + MapTrackerCatToNewznab(categoryID) + " (" + categoryID + ")");
|
||||
var newznab = MapTrackerCatToNewznab(categoryID);
|
||||
output("Category: " + MapTrackerCatToNewznab(categoryID).First().ToString() + " (" + categoryID + ")");
|
||||
|
||||
// Seeders
|
||||
int seeders = ParseUtil.CoerceInt(Regex.Match(tRow.Find("td:eq(5)").Text(), @"\d+").Value);
|
||||
@@ -345,30 +333,46 @@ namespace Jackett.Indexers
|
||||
output("Comments Link: " + commentsLink.AbsoluteUri);
|
||||
|
||||
// Torrent Download URL
|
||||
Uri downloadLink = new Uri(TorrentDownloadUrl.Replace("{id}", id.ToString()).Replace("{auth_key}", ConfigData.AuthKey.Value).Replace("{torrent_pass}", ConfigData.TorrentPass.Value));
|
||||
output("Download Link: " + downloadLink.AbsoluteUri);
|
||||
Uri downloadLink = null;
|
||||
string link = tRow.Find("td:eq(3) > a").Attr("href");
|
||||
if (!String.IsNullOrEmpty(link))
|
||||
{
|
||||
// Download link available
|
||||
downloadLink = new Uri(SiteLink + link);
|
||||
output("Download Link: " + downloadLink.AbsoluteUri);
|
||||
}
|
||||
else
|
||||
{
|
||||
// No download link available -- Must be on pending ( can't be downloaded now...)
|
||||
output("Download Link: Not available, torrent pending ? Skipping ...");
|
||||
continue;
|
||||
}
|
||||
|
||||
// Freeleech
|
||||
int downloadVolumeFactor = 1;
|
||||
if (tRow.Find("img[alt=\"Freeleech\"]").Length >= 1)
|
||||
{
|
||||
downloadVolumeFactor = 0;
|
||||
output("FreeLeech =)");
|
||||
}
|
||||
|
||||
// Building release infos
|
||||
var release = new ReleaseInfo();
|
||||
release.Category = MapTrackerCatToNewznab(categoryID.ToString());
|
||||
release.Title = name;
|
||||
release.Seeders = seeders;
|
||||
release.Peers = seeders + leechers;
|
||||
release.MinimumRatio = 1;
|
||||
release.MinimumSeedTime = 172800;
|
||||
release.PublishDate = date;
|
||||
release.Size = size;
|
||||
release.Guid = detailsLink;
|
||||
release.Comments = commentsLink;
|
||||
release.Link = downloadLink;
|
||||
|
||||
// freeleech
|
||||
if (tRow.Find("img[alt=\"Freeleech\"]").Length >= 1)
|
||||
release.DownloadVolumeFactor = 0;
|
||||
else
|
||||
release.DownloadVolumeFactor = 1;
|
||||
release.UploadVolumeFactor = 1;
|
||||
|
||||
var release = new ReleaseInfo()
|
||||
{
|
||||
Category = MapTrackerCatToNewznab(categoryID.ToString()),
|
||||
Title = name,
|
||||
Seeders = seeders,
|
||||
Peers = seeders + leechers,
|
||||
MinimumRatio = 1,
|
||||
MinimumSeedTime = 172800,
|
||||
PublishDate = date,
|
||||
Size = size,
|
||||
Guid = detailsLink,
|
||||
Comments = commentsLink,
|
||||
Link = downloadLink,
|
||||
UploadVolumeFactor = 1,
|
||||
DownloadVolumeFactor = downloadVolumeFactor
|
||||
};
|
||||
releases.Add(release);
|
||||
}
|
||||
|
||||
@@ -446,9 +450,9 @@ namespace Jackett.Indexers
|
||||
/// </summary>
|
||||
/// <param name="request">URL created by Query Builder</param>
|
||||
/// <returns>Results from query</returns>
|
||||
private async Task<WebClientStringResult> queryExec(string request)
|
||||
private async Task<String> queryExec(string request)
|
||||
{
|
||||
WebClientStringResult results = null;
|
||||
String results = null;
|
||||
|
||||
// Switch in we are in DEV mode with Hard Drive Cache or not
|
||||
if (DevMode && CacheMode)
|
||||
@@ -469,25 +473,40 @@ namespace Jackett.Indexers
|
||||
/// </summary>
|
||||
/// <param name="request">URL created by Query Builder</param>
|
||||
/// <returns>Results from query</returns>
|
||||
private async Task<WebClientStringResult> queryCache(string request)
|
||||
private async Task<String> queryCache(string request)
|
||||
{
|
||||
WebClientStringResult results = null;
|
||||
String results;
|
||||
|
||||
// Create Directory if not exist
|
||||
System.IO.Directory.CreateDirectory(directory);
|
||||
System.IO.Directory.CreateDirectory(Directory);
|
||||
|
||||
// Clean Storage Provider Directory from outdated cached queries
|
||||
cleanCacheStorage();
|
||||
|
||||
// File Name
|
||||
string fileName = StringUtil.HashSHA1(request) + ".json";
|
||||
|
||||
// Create fingerprint for request
|
||||
string file = directory + request.GetHashCode() + ".json";
|
||||
string file = Path.Combine(Directory, fileName);
|
||||
|
||||
// Checking modes states
|
||||
if (System.IO.File.Exists(file))
|
||||
if (File.Exists(file))
|
||||
{
|
||||
// File exist... loading it right now !
|
||||
output("Loading results from hard drive cache ..." + request.GetHashCode() + ".json");
|
||||
results = JsonConvert.DeserializeObject<WebClientStringResult>(System.IO.File.ReadAllText(file));
|
||||
output("Loading results from hard drive cache ..." + fileName);
|
||||
try
|
||||
{
|
||||
using (StreamReader fileReader = File.OpenText(file))
|
||||
{
|
||||
JsonSerializer serializer = new JsonSerializer();
|
||||
results = (String)serializer.Deserialize(fileReader, typeof(String));
|
||||
}
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
output("Error loading cached results ! " + e.Message, "error");
|
||||
results = null;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -495,8 +514,12 @@ namespace Jackett.Indexers
|
||||
results = await queryTracker(request);
|
||||
|
||||
// Cached file didn't exist for our query, writing it right now !
|
||||
output("Writing results to hard drive cache ..." + request.GetHashCode() + ".json");
|
||||
System.IO.File.WriteAllText(file, JsonConvert.SerializeObject(results));
|
||||
output("Writing results to hard drive cache ..." + fileName);
|
||||
using (StreamWriter fileWriter = File.CreateText(file))
|
||||
{
|
||||
JsonSerializer serializer = new JsonSerializer();
|
||||
serializer.Serialize(fileWriter, results);
|
||||
}
|
||||
}
|
||||
return results;
|
||||
}
|
||||
@@ -506,7 +529,7 @@ namespace Jackett.Indexers
|
||||
/// </summary>
|
||||
/// <param name="request">URL created by Query Builder</param>
|
||||
/// <returns>Results from query</returns>
|
||||
private async Task<WebClientStringResult> queryTracker(string request)
|
||||
private async Task<String> queryTracker(string request)
|
||||
{
|
||||
WebClientStringResult results = null;
|
||||
|
||||
@@ -518,26 +541,26 @@ namespace Jackett.Indexers
|
||||
results = await RequestStringWithCookiesAndRetry(request, null, null, emulatedBrowserHeaders);
|
||||
|
||||
// Return results from tracker
|
||||
return results;
|
||||
return results.Content;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Clean Hard Drive Cache Storage
|
||||
/// </summary>
|
||||
/// <param name="force">Force Provider Folder deletion</param>
|
||||
private void cleanCacheStorage(Boolean force = false)
|
||||
private void cleanCacheStorage(bool force = false)
|
||||
{
|
||||
// Check cleaning method
|
||||
if(force)
|
||||
if (force)
|
||||
{
|
||||
// Deleting Provider Storage folder and all files recursively
|
||||
output("\nDeleting Provider Storage folder and all files recursively ...");
|
||||
|
||||
|
||||
// Check if directory exist
|
||||
if(System.IO.Directory.Exists(directory))
|
||||
if (System.IO.Directory.Exists(Directory))
|
||||
{
|
||||
// Delete storage directory of provider
|
||||
System.IO.Directory.Delete(directory, true);
|
||||
System.IO.Directory.Delete(Directory, true);
|
||||
output("-> Storage folder deleted successfully.");
|
||||
}
|
||||
else
|
||||
@@ -548,24 +571,27 @@ namespace Jackett.Indexers
|
||||
}
|
||||
else
|
||||
{
|
||||
int i = 0;
|
||||
var i = 0;
|
||||
// Check if there is file older than ... and delete them
|
||||
output("\nCleaning Provider Storage folder... in progress.");
|
||||
System.IO.Directory.GetFiles(directory)
|
||||
.Select(f => new System.IO.FileInfo(f))
|
||||
System.IO.Directory.GetFiles(Directory)
|
||||
.Select(f => new FileInfo(f))
|
||||
.Where(f => f.LastAccessTime < DateTime.Now.AddMilliseconds(-Convert.ToInt32(ConfigData.HardDriveCacheKeepTime.Value)))
|
||||
.ToList()
|
||||
.ForEach(f => {
|
||||
.ForEach(f =>
|
||||
{
|
||||
output("Deleting cached file << " + f.Name + " >> ... done.");
|
||||
f.Delete();
|
||||
i++;
|
||||
});
|
||||
});
|
||||
|
||||
// Inform on what was cleaned during process
|
||||
if(i > 0) {
|
||||
if (i > 0)
|
||||
{
|
||||
output("-> Deleted " + i + " cached files during cleaning.");
|
||||
}
|
||||
else {
|
||||
else
|
||||
{
|
||||
output("-> Nothing deleted during cleaning.");
|
||||
}
|
||||
}
|
||||
@@ -577,7 +603,7 @@ namespace Jackett.Indexers
|
||||
private void latencyNow()
|
||||
{
|
||||
// Need latency ?
|
||||
if(Latency)
|
||||
if (Latency)
|
||||
{
|
||||
// Generate a random value in our range
|
||||
var random = new Random(DateTime.Now.Millisecond);
|
||||
@@ -696,7 +722,7 @@ namespace Jackett.Indexers
|
||||
private void output(string message, string level = "debug")
|
||||
{
|
||||
// Check if we are in dev mode
|
||||
if(DevMode)
|
||||
if (DevMode)
|
||||
{
|
||||
// Output message to console
|
||||
Console.WriteLine(message);
|
||||
|
||||
@@ -18,7 +18,7 @@ using Jackett.Models.IndexerConfig;
|
||||
|
||||
namespace Jackett.Indexers
|
||||
{
|
||||
public abstract class AvistazTracker : BaseIndexer
|
||||
public abstract class AvistazTracker : BaseWebIndexer
|
||||
{
|
||||
private string LoginUrl { get { return SiteLink + "auth/login"; } }
|
||||
private string SearchUrl { get { return SiteLink + "torrents?in=1&type={0}&search={1}"; } }
|
||||
@@ -29,12 +29,12 @@ namespace Jackett.Indexers
|
||||
set { base.configData = value; }
|
||||
}
|
||||
|
||||
public AvistazTracker(IIndexerManagerService indexerManager, IWebClient webClient, Logger logger, IProtectionService protectionService, string name, string desc, string link)
|
||||
public AvistazTracker(IIndexerConfigurationService configService, IWebClient webClient, Logger logger, IProtectionService protectionService, string name, string desc, string link)
|
||||
: base(name: name,
|
||||
description: desc,
|
||||
link: link,
|
||||
caps: TorznabUtil.CreateDefaultTorznabTVCaps(),
|
||||
manager: indexerManager,
|
||||
configService: configService,
|
||||
client: webClient,
|
||||
logger: logger,
|
||||
p: protectionService,
|
||||
@@ -51,7 +51,7 @@ namespace Jackett.Indexers
|
||||
AddCategoryMapping(3, TorznabCatType.Audio);
|
||||
}
|
||||
|
||||
public async Task<IndexerConfigurationStatus> ApplyConfiguration(JToken configJson)
|
||||
public override async Task<IndexerConfigurationStatus> ApplyConfiguration(JToken configJson)
|
||||
{
|
||||
LoadValuesFromJson(configJson);
|
||||
var loginPage = await RequestStringWithCookies(LoginUrl, string.Empty);
|
||||
@@ -75,7 +75,7 @@ namespace Jackett.Indexers
|
||||
return IndexerConfigurationStatus.RequiresTesting;
|
||||
}
|
||||
|
||||
public async Task<IEnumerable<ReleaseInfo>> PerformQuery(TorznabQuery query)
|
||||
protected override async Task<IEnumerable<ReleaseInfo>> PerformQuery(TorznabQuery query)
|
||||
{
|
||||
var releases = new List<ReleaseInfo>();
|
||||
|
||||
|
||||
@@ -17,7 +17,7 @@ using System.Web;
|
||||
|
||||
namespace Jackett.Indexers.Abstract
|
||||
{
|
||||
public abstract class GazelleTracker : BaseIndexer
|
||||
public abstract class GazelleTracker : BaseWebIndexer
|
||||
{
|
||||
protected string LoginUrl { get { return SiteLink + "login.php"; } }
|
||||
protected string APIUrl { get { return SiteLink + "ajax.php"; } }
|
||||
@@ -30,12 +30,12 @@ namespace Jackett.Indexers.Abstract
|
||||
set { base.configData = value; }
|
||||
}
|
||||
|
||||
public GazelleTracker(IIndexerManagerService indexerManager, IWebClient webClient, Logger logger, IProtectionService protectionService, string name, string desc, string link)
|
||||
public GazelleTracker(IIndexerConfigurationService configService, IWebClient webClient, Logger logger, IProtectionService protectionService, string name, string desc, string link)
|
||||
: base(name: name,
|
||||
description: desc,
|
||||
link: link,
|
||||
caps: new TorznabCapabilities(),
|
||||
manager: indexerManager,
|
||||
configService: configService,
|
||||
client: webClient,
|
||||
logger: logger,
|
||||
p: protectionService,
|
||||
@@ -44,7 +44,7 @@ namespace Jackett.Indexers.Abstract
|
||||
Encoding = Encoding.GetEncoding("UTF-8");
|
||||
}
|
||||
|
||||
public async Task<IndexerConfigurationStatus> ApplyConfiguration(JToken configJson)
|
||||
public override async Task<IndexerConfigurationStatus> ApplyConfiguration(JToken configJson)
|
||||
{
|
||||
LoadValuesFromJson(configJson);
|
||||
var pairs = new Dictionary<string, string> {
|
||||
@@ -68,7 +68,7 @@ namespace Jackett.Indexers.Abstract
|
||||
return IndexerConfigurationStatus.RequiresTesting;
|
||||
}
|
||||
|
||||
public async Task<IEnumerable<ReleaseInfo>> PerformQuery(TorznabQuery query)
|
||||
protected override async Task<IEnumerable<ReleaseInfo>> PerformQuery(TorznabQuery query)
|
||||
{
|
||||
var releases = new List<ReleaseInfo>();
|
||||
var searchString = query.GetQueryString();
|
||||
|
||||
@@ -19,19 +19,19 @@ using System.Collections.Specialized;
|
||||
|
||||
namespace Jackett.Indexers
|
||||
{
|
||||
public class AlphaRatio : BaseIndexer, IIndexer
|
||||
public class AlphaRatio : BaseWebIndexer
|
||||
{
|
||||
private string LoginUrl { get { return SiteLink + "login.php"; } }
|
||||
private string SearchUrl { get { return SiteLink + "ajax.php?action=browse&order_by=time&order_way=desc&"; } }
|
||||
private string DownloadUrl { get { return SiteLink + "torrents.php?action=download&id="; } }
|
||||
private string GuidUrl { get { return SiteLink + "torrents.php?torrentid="; } }
|
||||
|
||||
public AlphaRatio(IIndexerManagerService i, IWebClient w, Logger l, IProtectionService ps)
|
||||
public AlphaRatio(IIndexerConfigurationService configService, IWebClient w, Logger l, IProtectionService ps)
|
||||
: base(name: "AlphaRatio",
|
||||
description: "Legendary",
|
||||
link: "https://alpharatio.cc/",
|
||||
caps: new TorznabCapabilities(),
|
||||
manager: i,
|
||||
configService: configService,
|
||||
client: w,
|
||||
logger: l,
|
||||
p: ps,
|
||||
@@ -71,7 +71,7 @@ namespace Jackett.Indexers
|
||||
set { base.configData = value; }
|
||||
}
|
||||
|
||||
public async Task<IndexerConfigurationStatus> ApplyConfiguration(JToken configJson)
|
||||
public override async Task<IndexerConfigurationStatus> ApplyConfiguration(JToken configJson)
|
||||
{
|
||||
LoadValuesFromJson(configJson);
|
||||
var pairs = new Dictionary<string, string> {
|
||||
@@ -123,7 +123,7 @@ namespace Jackett.Indexers
|
||||
|
||||
}
|
||||
|
||||
public async Task<IEnumerable<ReleaseInfo>> PerformQuery(TorznabQuery query)
|
||||
protected override async Task<IEnumerable<ReleaseInfo>> PerformQuery(TorznabQuery query)
|
||||
{
|
||||
var releases = new List<ReleaseInfo>();
|
||||
var searchString = query.GetQueryString();
|
||||
|
||||
@@ -16,7 +16,7 @@ using System.Linq;
|
||||
|
||||
namespace Jackett.Indexers
|
||||
{
|
||||
public class Andraste : BaseIndexer, IIndexer
|
||||
public class Andraste : BaseWebIndexer
|
||||
{
|
||||
string LoginUrl { get { return SiteLink + "takelogin.php"; } }
|
||||
string BrowseUrl { get { return SiteLink + "browse.php"; } }
|
||||
@@ -27,12 +27,12 @@ namespace Jackett.Indexers
|
||||
set { base.configData = value; }
|
||||
}
|
||||
|
||||
public Andraste(IIndexerManagerService i, IWebClient wc, Logger l, IProtectionService ps)
|
||||
public Andraste(IIndexerConfigurationService configService, IWebClient wc, Logger l, IProtectionService ps)
|
||||
: base(name: "Andraste",
|
||||
description: "A German general tracker.",
|
||||
link: "https://andraste.io/",
|
||||
caps: TorznabUtil.CreateDefaultTorznabTVCaps(),
|
||||
manager: i,
|
||||
configService: configService,
|
||||
client: wc,
|
||||
logger: l,
|
||||
p: ps,
|
||||
@@ -74,7 +74,7 @@ namespace Jackett.Indexers
|
||||
AddCategoryMapping(32, TorznabCatType.XXX); // XXX
|
||||
}
|
||||
|
||||
public async Task<IndexerConfigurationStatus> ApplyConfiguration(JToken configJson)
|
||||
public override async Task<IndexerConfigurationStatus> ApplyConfiguration(JToken configJson)
|
||||
{
|
||||
LoadValuesFromJson(configJson);
|
||||
|
||||
@@ -95,7 +95,7 @@ namespace Jackett.Indexers
|
||||
return IndexerConfigurationStatus.RequiresTesting;
|
||||
}
|
||||
|
||||
public async Task<IEnumerable<ReleaseInfo>> PerformQuery(TorznabQuery query)
|
||||
protected override async Task<IEnumerable<ReleaseInfo>> PerformQuery(TorznabQuery query)
|
||||
{
|
||||
TimeZoneInfo.TransitionTime startTransition = TimeZoneInfo.TransitionTime.CreateFloatingDateRule(new DateTime(1, 1, 1, 3, 0, 0), 3, 5, DayOfWeek.Sunday);
|
||||
TimeZoneInfo.TransitionTime endTransition = TimeZoneInfo.TransitionTime.CreateFloatingDateRule(new DateTime(1, 1, 1, 4, 0, 0), 10, 5, DayOfWeek.Sunday);
|
||||
|
||||
@@ -22,7 +22,7 @@ using System.Web;
|
||||
|
||||
namespace Jackett.Indexers
|
||||
{
|
||||
public class AnimeBytes : BaseIndexer, IIndexer
|
||||
public class AnimeBytes : BaseCachingWebIndexer
|
||||
{
|
||||
enum SearchType
|
||||
{
|
||||
@@ -34,7 +34,7 @@ namespace Jackett.Indexers
|
||||
private string SearchUrl { get { return SiteLink + "torrents.php?"; } }
|
||||
private string MusicSearchUrl { get { return SiteLink + "torrents2.php?"; } }
|
||||
public bool AllowRaws { get { return configData.IncludeRaw.Value; } }
|
||||
public bool InsertSeason { get { return configData.InsertSeason!=null && configData.InsertSeason.Value; } }
|
||||
public bool InsertSeason { get { return configData.InsertSeason != null && configData.InsertSeason.Value; } }
|
||||
|
||||
new ConfigurationDataAnimeBytes configData
|
||||
{
|
||||
@@ -42,11 +42,11 @@ namespace Jackett.Indexers
|
||||
set { base.configData = value; }
|
||||
}
|
||||
|
||||
public AnimeBytes(IIndexerManagerService i, IWebClient client, Logger l, IProtectionService ps)
|
||||
public AnimeBytes(IIndexerConfigurationService configService, IWebClient client, Logger l, IProtectionService ps)
|
||||
: base(name: "AnimeBytes",
|
||||
link: "https://animebytes.tv/",
|
||||
description: "Powered by Tentacles",
|
||||
manager: i,
|
||||
configService: configService,
|
||||
client: client,
|
||||
caps: new TorznabCapabilities(TorznabCatType.TVAnime,
|
||||
TorznabCatType.Movies,
|
||||
@@ -64,16 +64,18 @@ namespace Jackett.Indexers
|
||||
Encoding = Encoding.GetEncoding("UTF-8");
|
||||
Language = "en-us";
|
||||
Type = "private";
|
||||
|
||||
webclient.EmulateBrowser = false; // Animebytes doesn't like fake user agents (issue #1535)
|
||||
}
|
||||
|
||||
|
||||
public override IEnumerable<ReleaseInfo> FilterResults(TorznabQuery query, IEnumerable<ReleaseInfo> input)
|
||||
protected override IEnumerable<ReleaseInfo> FilterResults(TorznabQuery query, IEnumerable<ReleaseInfo> input)
|
||||
{
|
||||
// Prevent filtering
|
||||
return input;
|
||||
}
|
||||
|
||||
public async Task<IndexerConfigurationStatus> ApplyConfiguration(JToken configJson)
|
||||
public override async Task<IndexerConfigurationStatus> ApplyConfiguration(JToken configJson)
|
||||
{
|
||||
LoadValuesFromJson(configJson);
|
||||
|
||||
@@ -86,7 +88,7 @@ namespace Jackett.Indexers
|
||||
var loginPage = await webclient.GetString(new Utils.Clients.WebRequest()
|
||||
{
|
||||
Url = LoginUrl,
|
||||
Encoding = Encoding
|
||||
Encoding = Encoding,
|
||||
});
|
||||
|
||||
CQ loginPageDom = loginPage.Content;
|
||||
@@ -112,7 +114,7 @@ namespace Jackett.Indexers
|
||||
Referer = LoginUrl,
|
||||
Type = RequestType.POST,
|
||||
Encoding = Encoding,
|
||||
Url = LoginUrl
|
||||
Url = LoginUrl,
|
||||
};
|
||||
var response = await RequestLoginAndFollowRedirect(LoginUrl, pairs, loginPage.Cookies, true, null);
|
||||
|
||||
@@ -128,21 +130,6 @@ namespace Jackett.Indexers
|
||||
return IndexerConfigurationStatus.RequiresTesting;
|
||||
}
|
||||
|
||||
// Override to load legacy config format
|
||||
public override void LoadFromSavedConfiguration(JToken jsonConfig)
|
||||
{
|
||||
if (jsonConfig is JObject)
|
||||
{
|
||||
configData.CookieHeader.Value = jsonConfig.Value<string>("cookies");
|
||||
configData.IncludeRaw.Value = jsonConfig.Value<bool>("raws");
|
||||
IsConfigured = true;
|
||||
SaveConfig();
|
||||
return;
|
||||
}
|
||||
|
||||
base.LoadFromSavedConfiguration(jsonConfig);
|
||||
}
|
||||
|
||||
private string StripEpisodeNumber(string term)
|
||||
{
|
||||
// Tracer does not support searching with episode number so strip it if we have one
|
||||
@@ -151,11 +138,11 @@ namespace Jackett.Indexers
|
||||
return term;
|
||||
}
|
||||
|
||||
public async Task<IEnumerable<ReleaseInfo>> PerformQuery(TorznabQuery query)
|
||||
protected override async Task<IEnumerable<ReleaseInfo>> PerformQuery(TorznabQuery query)
|
||||
{
|
||||
// The result list
|
||||
var releases = new List<ReleaseInfo>();
|
||||
|
||||
|
||||
if (ContainsMusicCategories(query.Categories))
|
||||
{
|
||||
foreach (var result in await GetResults(SearchType.Audio, query.SanitizedSearchTerm))
|
||||
@@ -210,7 +197,7 @@ namespace Jackett.Indexers
|
||||
if (cachedResult != null)
|
||||
return cachedResult.Results.Select(s => (ReleaseInfo)s.Clone()).ToArray();
|
||||
}
|
||||
|
||||
|
||||
// Get the content from the tracker
|
||||
var response = await RequestStringWithCookiesAndRetry(queryUrl);
|
||||
if (response.IsRedirect)
|
||||
@@ -302,7 +289,7 @@ namespace Jackett.Indexers
|
||||
releaseInfo = releaseInfo.Replace("Season ", "S");
|
||||
releaseInfo = releaseInfo.Trim();
|
||||
int test = 0;
|
||||
if (InsertSeason && int.TryParse(releaseInfo, out test) && releaseInfo.Length<=3)
|
||||
if (InsertSeason && int.TryParse(releaseInfo, out test) && releaseInfo.Length <= 3)
|
||||
{
|
||||
releaseInfo = "E0" + releaseInfo;
|
||||
}
|
||||
|
||||
@@ -20,7 +20,7 @@ using System.Globalization;
|
||||
|
||||
namespace Jackett.Indexers
|
||||
{
|
||||
public class AnimeTorrents : BaseIndexer, IIndexer
|
||||
public class AnimeTorrents : BaseWebIndexer
|
||||
{
|
||||
private string LoginUrl { get { return SiteLink + "login.php"; } }
|
||||
private string SearchUrl { get { return SiteLink + "ajax/torrents_data.php"; } }
|
||||
@@ -32,13 +32,13 @@ namespace Jackett.Indexers
|
||||
set { base.configData = value; }
|
||||
}
|
||||
|
||||
public AnimeTorrents(IIndexerManagerService i, HttpWebClient c, Logger l, IProtectionService ps)
|
||||
public AnimeTorrents(IIndexerConfigurationService configService, IWebClient c, Logger l, IProtectionService ps)
|
||||
: base(name: "AnimeTorrents",
|
||||
description: "Definitive source for anime and manga",
|
||||
link: "https://animetorrents.me/",
|
||||
caps: new TorznabCapabilities(),
|
||||
manager: i,
|
||||
client: c, // Forced HTTP client for custom headers
|
||||
configService: configService,
|
||||
client: c,
|
||||
logger: l,
|
||||
p: ps,
|
||||
configData: new ConfigurationDataBasicLogin())
|
||||
@@ -68,7 +68,7 @@ namespace Jackett.Indexers
|
||||
AddCategoryMapping(19, TorznabCatType.Audio); // OST
|
||||
}
|
||||
|
||||
public async Task<IndexerConfigurationStatus> ApplyConfiguration(JToken configJson)
|
||||
public override async Task<IndexerConfigurationStatus> ApplyConfiguration(JToken configJson)
|
||||
{
|
||||
LoadValuesFromJson(configJson);
|
||||
var pairs = new Dictionary<string, string> {
|
||||
@@ -91,7 +91,7 @@ namespace Jackett.Indexers
|
||||
return IndexerConfigurationStatus.RequiresTesting;
|
||||
}
|
||||
|
||||
public async Task<IEnumerable<ReleaseInfo>> PerformQuery(TorznabQuery query)
|
||||
protected override async Task<IEnumerable<ReleaseInfo>> PerformQuery(TorznabQuery query)
|
||||
{
|
||||
var releases = new List<ReleaseInfo>();
|
||||
var searchString = query.GetQueryString();
|
||||
|
||||
@@ -5,11 +5,9 @@ using System.Linq;
|
||||
using System.Text;
|
||||
using System.Text.RegularExpressions;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
using AngleSharp.Parser.Html;
|
||||
using Newtonsoft.Json.Linq;
|
||||
using NLog;
|
||||
|
||||
using Jackett.Models;
|
||||
using Jackett.Models.IndexerConfig;
|
||||
using Jackett.Services;
|
||||
@@ -18,7 +16,7 @@ using Jackett.Utils.Clients;
|
||||
|
||||
namespace Jackett.Indexers
|
||||
{
|
||||
class ArcheTorrent : BaseIndexer, IIndexer
|
||||
class ArcheTorrent : BaseWebIndexer
|
||||
{
|
||||
string LoginUrl { get { return SiteLink + "account-login.php"; } }
|
||||
string BrowseUrl { get { return SiteLink + "torrents-search.php"; } }
|
||||
@@ -30,11 +28,11 @@ namespace Jackett.Indexers
|
||||
set { base.configData = value; }
|
||||
}
|
||||
|
||||
public ArcheTorrent(IIndexerManagerService indexerManager, IWebClient webClient, Logger logger, IProtectionService protectionService)
|
||||
public ArcheTorrent(IIndexerConfigurationService configService, IWebClient webClient, Logger logger, IProtectionService protectionService)
|
||||
: base(name: "Arche Torrent",
|
||||
description: "French Torrent Tracker",
|
||||
link: "https://www.archetorrent.com/",
|
||||
manager: indexerManager,
|
||||
configService: configService,
|
||||
logger: logger,
|
||||
p: protectionService,
|
||||
client: webClient,
|
||||
@@ -81,10 +79,7 @@ namespace Jackett.Indexers
|
||||
AddCategoryMapping (14, TorznabCatType.ConsoleXbox360, "Jeux: Xbox360");
|
||||
AddCategoryMapping (44, TorznabCatType.ConsoleWii, "Jeux: Wii");
|
||||
AddCategoryMapping (45, TorznabCatType.ConsoleNDS, "Jeux: DS");
|
||||
AddCategoryMapping (71, TorznabCatType.AudioMP3, "Musique: TAT Music Release");
|
||||
AddCategoryMapping (26, TorznabCatType.AudioMP3, "Musique: Mp3");
|
||||
AddCategoryMapping (27, TorznabCatType.AudioVideo, "Musique: Clip Video");
|
||||
AddCategoryMapping (58, TorznabCatType.AudioLossless, "Musique: Flac");
|
||||
AddCategoryMapping (62, TorznabCatType.TVSD, "Serie tv: TV BDRip");
|
||||
AddCategoryMapping (5, TorznabCatType.TVSD, "Serie tv: Dvdrip");
|
||||
AddCategoryMapping (41, TorznabCatType.TVHD, "Serie tv: Hd");
|
||||
@@ -98,7 +93,7 @@ namespace Jackett.Indexers
|
||||
AddCategoryMapping (61, TorznabCatType.TVSD, "Tv: DVDRip");
|
||||
}
|
||||
|
||||
public async Task<IndexerConfigurationStatus> ApplyConfiguration(JToken configJson)
|
||||
public override async Task<IndexerConfigurationStatus> ApplyConfiguration(JToken configJson)
|
||||
{
|
||||
configData.LoadValuesFromJson(configJson);
|
||||
|
||||
@@ -119,7 +114,7 @@ namespace Jackett.Indexers
|
||||
return IndexerConfigurationStatus.RequiresTesting;
|
||||
}
|
||||
|
||||
public async Task<IEnumerable<ReleaseInfo>> PerformQuery(TorznabQuery query)
|
||||
protected override async Task<IEnumerable<ReleaseInfo>> PerformQuery(TorznabQuery query)
|
||||
{
|
||||
var releases = new List<ReleaseInfo>();
|
||||
var searchString = query.GetQueryString();
|
||||
|
||||
@@ -18,13 +18,13 @@ using Jackett.Models.IndexerConfig;
|
||||
|
||||
namespace Jackett.Indexers
|
||||
{
|
||||
public class Avistaz : AvistazTracker, IIndexer
|
||||
public class Avistaz : AvistazTracker
|
||||
{
|
||||
public Avistaz(IIndexerManagerService indexerManager, IWebClient webClient, Logger logger, IProtectionService protectionService)
|
||||
public Avistaz(IIndexerConfigurationService configService, IWebClient webClient, Logger logger, IProtectionService protectionService)
|
||||
: base(name: "Avistaz",
|
||||
desc: "Aka AsiaTorrents",
|
||||
link: "https://avistaz.to/",
|
||||
indexerManager: indexerManager,
|
||||
configService: configService,
|
||||
logger: logger,
|
||||
protectionService: protectionService,
|
||||
webClient: webClient
|
||||
|
||||
@@ -11,6 +11,7 @@ using System.Linq;
|
||||
using System.Net;
|
||||
using System.Net.Http;
|
||||
using System.Text;
|
||||
using System.Text.RegularExpressions;
|
||||
using System.Threading.Tasks;
|
||||
using System.Web;
|
||||
using Jackett.Models.IndexerConfig;
|
||||
@@ -20,7 +21,7 @@ namespace Jackett.Indexers
|
||||
{
|
||||
// To comply with the rules for this tracker, only the acronym is used and no publicly displayed URLs to the site.
|
||||
|
||||
public class BB : BaseIndexer, IIndexer
|
||||
public class BB : BaseWebIndexer
|
||||
{
|
||||
private string BaseUrl { get { return StringUtil.FromBase64("aHR0cHM6Ly9iYWNvbmJpdHMub3JnLw=="); } }
|
||||
private Uri BaseUri { get { return new Uri(BaseUrl); } }
|
||||
@@ -33,12 +34,12 @@ namespace Jackett.Indexers
|
||||
set { base.configData = value; }
|
||||
}
|
||||
|
||||
public BB(IIndexerManagerService i, Logger l, IWebClient w, IProtectionService ps)
|
||||
public BB(IIndexerConfigurationService configService, IWebClient w, Logger l, IProtectionService ps)
|
||||
: base(name: "bB",
|
||||
description: "bB",
|
||||
link: "https://baconbits.org/",
|
||||
link: StringUtil.FromBase64("aHR0cHM6Ly9iYWNvbmJpdHMub3JnLw=="),
|
||||
caps: new TorznabCapabilities(),
|
||||
manager: i,
|
||||
configService: configService,
|
||||
client: w,
|
||||
logger: l,
|
||||
p: ps,
|
||||
@@ -49,6 +50,8 @@ namespace Jackett.Indexers
|
||||
Type = "private";
|
||||
|
||||
AddCategoryMapping(1, TorznabCatType.Audio);
|
||||
AddCategoryMapping(1, TorznabCatType.AudioMP3);
|
||||
AddCategoryMapping(1, TorznabCatType.AudioLossless);
|
||||
AddCategoryMapping(2, TorznabCatType.PC);
|
||||
AddCategoryMapping(3, TorznabCatType.BooksEbook);
|
||||
AddCategoryMapping(4, TorznabCatType.AudioAudiobook);
|
||||
@@ -61,7 +64,7 @@ namespace Jackett.Indexers
|
||||
AddCategoryMapping(11, TorznabCatType.PCGames);
|
||||
}
|
||||
|
||||
public async Task<IndexerConfigurationStatus> ApplyConfiguration(JToken configJson)
|
||||
public override async Task<IndexerConfigurationStatus> ApplyConfiguration(JToken configJson)
|
||||
{
|
||||
LoadValuesFromJson(configJson);
|
||||
var pairs = new Dictionary<string, string> {
|
||||
@@ -89,91 +92,110 @@ namespace Jackett.Indexers
|
||||
return IndexerConfigurationStatus.RequiresTesting;
|
||||
}
|
||||
|
||||
public async Task<IEnumerable<ReleaseInfo>> PerformQuery(TorznabQuery query)
|
||||
protected override async Task<IEnumerable<ReleaseInfo>> PerformQuery(TorznabQuery query)
|
||||
{
|
||||
List<ReleaseInfo> releases = new List<ReleaseInfo>();
|
||||
List<string> searchStrings = new List<string>(new string[] { query.GetQueryString() });
|
||||
|
||||
if (string.IsNullOrEmpty(query.Episode) && (query.Season > 0))
|
||||
// Tracker naming rules: If query is for a whole season, "Season #" instead of "S##".
|
||||
searchStrings.Add((query.SanitizedSearchTerm + " " + string.Format("\"Season {0}\"", query.Season)).Trim());
|
||||
|
||||
var searchString = query.GetQueryString();
|
||||
var searchUrl = SearchUrl;
|
||||
var queryCollection = new NameValueCollection();
|
||||
|
||||
queryCollection.Add("action", "basic");
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(searchString))
|
||||
List<string> categories = MapTorznabCapsToTrackers(query);
|
||||
List<string> request_urls = new List<string>();
|
||||
|
||||
foreach (var searchString in searchStrings)
|
||||
{
|
||||
queryCollection.Add("searchstr", searchString);
|
||||
}
|
||||
var queryCollection = new NameValueCollection();
|
||||
queryCollection.Add("action", "basic");
|
||||
|
||||
foreach (var cat in MapTorznabCapsToTrackers(query))
|
||||
{
|
||||
queryCollection.Add("filter_cat[" + cat + "]", "1");
|
||||
}
|
||||
|
||||
searchUrl += queryCollection.GetQueryString();
|
||||
|
||||
var results = await RequestStringWithCookiesAndRetry(searchUrl);
|
||||
|
||||
// Occasionally the cookies become invalid, login again if that happens
|
||||
if (results.IsRedirect)
|
||||
{
|
||||
await ApplyConfiguration(null);
|
||||
results = await RequestStringWithCookiesAndRetry(searchUrl);
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
CQ dom = results.Content;
|
||||
var rows = dom["#torrent_table > tbody > tr.torrent"];
|
||||
foreach (var row in rows)
|
||||
if (!string.IsNullOrWhiteSpace(searchString))
|
||||
{
|
||||
CQ qRow = row.Cq();
|
||||
var release = new ReleaseInfo();
|
||||
|
||||
release.MinimumRatio = 1;
|
||||
release.MinimumSeedTime = 172800;
|
||||
|
||||
var catStr = row.ChildElements.ElementAt(0).FirstElementChild.GetAttribute("href").Split(new char[] { '[', ']' })[1];
|
||||
release.Category = MapTrackerCatToNewznab(catStr);
|
||||
|
||||
var qLink = row.ChildElements.ElementAt(1).Cq().Children("a")[0].Cq();
|
||||
var linkStr = qLink.Attr("href");
|
||||
release.Comments = new Uri(BaseUrl + "/" + linkStr);
|
||||
release.Guid = release.Comments;
|
||||
|
||||
var qDownload = row.ChildElements.ElementAt(1).Cq().Find("a[title='Download']")[0].Cq();
|
||||
release.Link = new Uri(BaseUrl + "/" + qDownload.Attr("href"));
|
||||
|
||||
var dateStr = row.ChildElements.ElementAt(3).Cq().Text().Trim().Replace(" and", "");
|
||||
release.PublishDate = DateTimeUtil.FromTimeAgo(dateStr);
|
||||
|
||||
var sizeStr = row.ChildElements.ElementAt(4).Cq().Text();
|
||||
release.Size = ReleaseInfo.GetBytes(sizeStr);
|
||||
|
||||
release.Files = ParseUtil.CoerceInt(row.ChildElements.ElementAt(2).Cq().Text().Trim());
|
||||
release.Grabs = ParseUtil.CoerceInt(row.ChildElements.ElementAt(6).Cq().Text().Trim());
|
||||
release.Seeders = ParseUtil.CoerceInt(row.ChildElements.ElementAt(7).Cq().Text().Trim());
|
||||
release.Peers = ParseUtil.CoerceInt(row.ChildElements.ElementAt(8).Cq().Text().Trim()) + release.Seeders;
|
||||
|
||||
var grabs = qRow.Find("td:nth-child(6)").Text();
|
||||
release.Grabs = ParseUtil.CoerceInt(grabs);
|
||||
|
||||
if (qRow.Find("strong:contains(\"Freeleech!\")").Length >= 1)
|
||||
release.DownloadVolumeFactor = 0;
|
||||
else
|
||||
release.DownloadVolumeFactor = 1;
|
||||
|
||||
release.UploadVolumeFactor = 1;
|
||||
|
||||
var title = qRow.Find("td:nth-child(2)");
|
||||
title.Find("span, strong, div, br").Remove();
|
||||
release.Title = ParseUtil.NormalizeMultiSpaces(title.Text().Replace(" - ]", "]"));
|
||||
|
||||
releases.Add(release);
|
||||
queryCollection.Add("searchstr", searchString);
|
||||
}
|
||||
|
||||
foreach (var cat in categories)
|
||||
{
|
||||
|
||||
queryCollection.Add("filter_cat[" + cat + "]", "1");
|
||||
}
|
||||
|
||||
request_urls.Add(SearchUrl + queryCollection.GetQueryString());
|
||||
}
|
||||
catch (Exception ex)
|
||||
IEnumerable<Task<WebClientStringResult>> downloadTasksQuery =
|
||||
from url in request_urls select RequestStringWithCookiesAndRetry(url);
|
||||
|
||||
WebClientStringResult[] responses = await Task.WhenAll(downloadTasksQuery.ToArray());
|
||||
|
||||
for (int i = 0; i < searchStrings.Count(); i++)
|
||||
{
|
||||
OnParseError(results.Content, ex);
|
||||
var results = responses[i];
|
||||
// Occasionally the cookies become invalid, login again if that happens
|
||||
if (results.IsRedirect)
|
||||
{
|
||||
await ApplyConfiguration(null);
|
||||
results = await RequestStringWithCookiesAndRetry(request_urls[i]);
|
||||
}
|
||||
try
|
||||
{
|
||||
CQ dom = results.Content;
|
||||
var rows = dom["#torrent_table > tbody > tr.torrent"];
|
||||
foreach (var row in rows)
|
||||
{
|
||||
CQ qRow = row.Cq();
|
||||
var release = new ReleaseInfo();
|
||||
|
||||
release.MinimumRatio = 1;
|
||||
release.MinimumSeedTime = 172800;
|
||||
|
||||
var catStr = row.ChildElements.ElementAt(0).FirstElementChild.GetAttribute("href").Split(new char[] { '[', ']' })[1];
|
||||
release.Category = MapTrackerCatToNewznab(catStr);
|
||||
|
||||
var qLink = row.ChildElements.ElementAt(1).Cq().Children("a")[0].Cq();
|
||||
var linkStr = qLink.Attr("href");
|
||||
release.Comments = new Uri(BaseUrl + "/" + linkStr);
|
||||
release.Guid = release.Comments;
|
||||
|
||||
var qDownload = row.ChildElements.ElementAt(1).Cq().Find("a[title='Download']")[0].Cq();
|
||||
release.Link = new Uri(BaseUrl + "/" + qDownload.Attr("href"));
|
||||
|
||||
var dateStr = row.ChildElements.ElementAt(3).Cq().Text().Trim().Replace(" and", "");
|
||||
release.PublishDate = DateTimeUtil.FromTimeAgo(dateStr);
|
||||
|
||||
var sizeStr = row.ChildElements.ElementAt(4).Cq().Text();
|
||||
release.Size = ReleaseInfo.GetBytes(sizeStr);
|
||||
|
||||
release.Files = ParseUtil.CoerceInt(row.ChildElements.ElementAt(2).Cq().Text().Trim());
|
||||
release.Grabs = ParseUtil.CoerceInt(row.ChildElements.ElementAt(6).Cq().Text().Trim());
|
||||
release.Seeders = ParseUtil.CoerceInt(row.ChildElements.ElementAt(7).Cq().Text().Trim());
|
||||
release.Peers = ParseUtil.CoerceInt(row.ChildElements.ElementAt(8).Cq().Text().Trim()) + release.Seeders;
|
||||
|
||||
var grabs = qRow.Find("td:nth-child(6)").Text();
|
||||
release.Grabs = ParseUtil.CoerceInt(grabs);
|
||||
|
||||
if (qRow.Find("strong:contains(\"Freeleech!\")").Length >= 1)
|
||||
release.DownloadVolumeFactor = 0;
|
||||
else
|
||||
release.DownloadVolumeFactor = 1;
|
||||
|
||||
release.UploadVolumeFactor = 1;
|
||||
|
||||
var title = qRow.Find("td:nth-child(2)");
|
||||
title.Find("span, strong, div, br").Remove();
|
||||
|
||||
release.Title = ParseUtil.NormalizeMultiSpaces(title.Text().Replace(" - ]", "]"));
|
||||
|
||||
if (catStr == "10") //change "Season #" to "S##" for TV shows
|
||||
release.Title = Regex.Replace(release.Title, @"Season (\d+)",
|
||||
m => string.Format("S{0:00}", Int32.Parse(m.Groups[1].Value)));
|
||||
|
||||
releases.Add(release);
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
OnParseError(results.Content, ex);
|
||||
}
|
||||
}
|
||||
return releases;
|
||||
}
|
||||
|
||||
@@ -17,7 +17,7 @@ using System.Text.RegularExpressions;
|
||||
|
||||
namespace Jackett.Indexers
|
||||
{
|
||||
public class BJShare : BaseIndexer, IIndexer
|
||||
public class BJShare : BaseWebIndexer
|
||||
{
|
||||
string LoginUrl { get { return SiteLink + "login.php"; } }
|
||||
string BrowseUrl { get { return SiteLink + "torrents.php"; } }
|
||||
@@ -29,12 +29,12 @@ namespace Jackett.Indexers
|
||||
set { base.configData = value; }
|
||||
}
|
||||
|
||||
public BJShare(IIndexerManagerService i, IWebClient wc, Logger l, IProtectionService ps)
|
||||
public BJShare(IIndexerConfigurationService configService, IWebClient wc, Logger l, IProtectionService ps)
|
||||
: base(name: "BJ-Share",
|
||||
description: "A brazilian tracker.",
|
||||
link: "https://bj-share.me/",
|
||||
caps: TorznabUtil.CreateDefaultTorznabTVCaps(),
|
||||
manager: i,
|
||||
configService: configService,
|
||||
client: wc,
|
||||
logger: l,
|
||||
p: ps,
|
||||
@@ -68,7 +68,7 @@ namespace Jackett.Indexers
|
||||
AddCategoryMapping(201, TorznabCatType.XXXImageset); // Fotos Adultas
|
||||
}
|
||||
|
||||
public async Task<IndexerConfigurationStatus> ApplyConfiguration(JToken configJson)
|
||||
public override async Task<IndexerConfigurationStatus> ApplyConfiguration(JToken configJson)
|
||||
{
|
||||
LoadValuesFromJson(configJson);
|
||||
|
||||
@@ -96,7 +96,7 @@ namespace Jackett.Indexers
|
||||
return term.Trim();
|
||||
}
|
||||
|
||||
public async Task<IEnumerable<ReleaseInfo>> PerformQuery(TorznabQuery query)
|
||||
protected override async Task<IEnumerable<ReleaseInfo>> PerformQuery(TorznabQuery query)
|
||||
{
|
||||
var releases = new List<ReleaseInfo>();
|
||||
|
||||
@@ -283,6 +283,7 @@ namespace Jackett.Indexers
|
||||
|
||||
release.Description = release.Description.Replace("Full HD", "1080p");
|
||||
release.Description = release.Description.Replace("/ HD / ", "/ 720p /");
|
||||
release.Description = release.Description.Replace(" / HD]", " / 720p]");
|
||||
release.Description = release.Description.Replace("4K", "2160p");
|
||||
|
||||
int nBarra = release.Title.IndexOf("[");
|
||||
|
||||
@@ -17,7 +17,7 @@ using Jackett.Models.IndexerConfig;
|
||||
|
||||
namespace Jackett.Indexers
|
||||
{
|
||||
public class BakaBT : BaseIndexer, IIndexer
|
||||
public class BakaBT : BaseWebIndexer
|
||||
{
|
||||
public string SearchUrl { get { return SiteLink + "browse.php?only=0&incomplete=1&lossless=1&hd=1&multiaudio=1&bonus=1&reorder=1&q="; } }
|
||||
public string LoginUrl { get { return SiteLink + "login.php"; } }
|
||||
@@ -29,12 +29,12 @@ namespace Jackett.Indexers
|
||||
set { base.configData = value; }
|
||||
}
|
||||
|
||||
public BakaBT(IIndexerManagerService i, IWebClient wc, Logger l, IProtectionService ps)
|
||||
public BakaBT(IIndexerConfigurationService configService, IWebClient wc, Logger l, IProtectionService ps)
|
||||
: base(name: "BakaBT",
|
||||
description: "Anime Comunity",
|
||||
link: "https://bakabt.me/",
|
||||
caps: new TorznabCapabilities(TorznabCatType.TVAnime),
|
||||
manager: i,
|
||||
configService: configService,
|
||||
client: wc,
|
||||
logger: l,
|
||||
p: ps,
|
||||
@@ -46,7 +46,7 @@ namespace Jackett.Indexers
|
||||
}
|
||||
|
||||
|
||||
public async Task<IndexerConfigurationStatus> ApplyConfiguration(JToken configJson)
|
||||
public override async Task<IndexerConfigurationStatus> ApplyConfiguration(JToken configJson)
|
||||
{
|
||||
LoadValuesFromJson(configJson);
|
||||
|
||||
@@ -75,7 +75,7 @@ namespace Jackett.Indexers
|
||||
return IndexerConfigurationStatus.RequiresTesting;
|
||||
}
|
||||
|
||||
public async Task<IEnumerable<ReleaseInfo>> PerformQuery(TorznabQuery query)
|
||||
protected override async Task<IEnumerable<ReleaseInfo>> PerformQuery(TorznabQuery query)
|
||||
{
|
||||
|
||||
// This tracker only deals with full seasons so chop off the episode/season number if we have it D:
|
||||
|
||||
@@ -10,33 +10,34 @@ using Jackett.Services;
|
||||
using Jackett.Utils;
|
||||
using Jackett.Utils.Clients;
|
||||
using AutoMapper;
|
||||
using System.Threading;
|
||||
using Jackett.Models.IndexerConfig;
|
||||
using System.Text.RegularExpressions;
|
||||
using Newtonsoft.Json;
|
||||
|
||||
namespace Jackett.Indexers
|
||||
{
|
||||
public abstract class BaseIndexer
|
||||
public abstract class BaseIndexer : IIndexer
|
||||
{
|
||||
public static string GetIndexerID(Type type)
|
||||
{
|
||||
return type.Name.ToLowerInvariant().StripNonAlphaNumeric();
|
||||
}
|
||||
|
||||
public string SiteLink { get; protected set; }
|
||||
public string DefaultSiteLink { get; protected set; }
|
||||
public string[] AlternativeSiteLinks { get; protected set; } = new string[] { };
|
||||
public string DisplayDescription { get; protected set; }
|
||||
public string DisplayName { get; protected set; }
|
||||
public string Language { get; protected set; }
|
||||
public Encoding Encoding { get; protected set; }
|
||||
public string Type { get; protected set; }
|
||||
public string ID { get { return GetIndexerID(GetType()); } }
|
||||
public virtual string ID { get { return GetIndexerID(GetType()); } }
|
||||
|
||||
public bool IsConfigured { get; protected set; }
|
||||
public TorznabCapabilities TorznabCaps { get; protected set; }
|
||||
public virtual bool IsConfigured { get; protected set; }
|
||||
protected Logger logger;
|
||||
protected IIndexerManagerService indexerService;
|
||||
protected static List<CachedQueryResult> cache = new List<CachedQueryResult>();
|
||||
protected static readonly TimeSpan cacheTime = new TimeSpan(0, 9, 0);
|
||||
protected IWebClient webclient;
|
||||
protected IIndexerConfigurationService configurationService;
|
||||
protected IProtectionService protectionService;
|
||||
protected readonly string downloadUrlBase = "";
|
||||
|
||||
protected ConfigurationData configData;
|
||||
|
||||
protected string CookieHeader
|
||||
{
|
||||
@@ -56,126 +57,25 @@ namespace Jackett.Indexers
|
||||
}
|
||||
}
|
||||
|
||||
protected ConfigurationData configData;
|
||||
|
||||
private List<CategoryMapping> categoryMapping = new List<CategoryMapping>();
|
||||
public abstract TorznabCapabilities TorznabCaps { get; protected set; }
|
||||
|
||||
// standard constructor used by most indexers
|
||||
public BaseIndexer(string name, string link, string description, IIndexerManagerService manager, IWebClient client, Logger logger, ConfigurationData configData, IProtectionService p, TorznabCapabilities caps = null, string downloadBase = null)
|
||||
: this(manager, client, logger, p)
|
||||
public BaseIndexer(string name, string link, string description, IIndexerConfigurationService configService, Logger logger, ConfigurationData configData, IProtectionService p)
|
||||
{
|
||||
if (!link.EndsWith("/"))
|
||||
this.logger = logger;
|
||||
configurationService = configService;
|
||||
protectionService = p;
|
||||
|
||||
if (!link.EndsWith("/", StringComparison.Ordinal))
|
||||
throw new Exception("Site link must end with a slash.");
|
||||
|
||||
DisplayName = name;
|
||||
DisplayDescription = description;
|
||||
SiteLink = link;
|
||||
DefaultSiteLink = link;
|
||||
this.downloadUrlBase = downloadBase;
|
||||
this.configData = configData;
|
||||
LoadValuesFromJson(null);
|
||||
|
||||
if (caps == null)
|
||||
caps = TorznabUtil.CreateDefaultTorznabTVCaps();
|
||||
TorznabCaps = caps;
|
||||
|
||||
}
|
||||
|
||||
// minimal constructor used by e.g. cardigann generic indexer
|
||||
public BaseIndexer(IIndexerManagerService manager, IWebClient client, Logger logger, IProtectionService p)
|
||||
{
|
||||
this.logger = logger;
|
||||
indexerService = manager;
|
||||
webclient = client;
|
||||
protectionService = p;
|
||||
}
|
||||
|
||||
public IEnumerable<ReleaseInfo> CleanLinks(IEnumerable<ReleaseInfo> releases)
|
||||
{
|
||||
if (string.IsNullOrEmpty(downloadUrlBase))
|
||||
return releases;
|
||||
foreach (var release in releases)
|
||||
{
|
||||
if (release.Link.ToString().StartsWith(downloadUrlBase))
|
||||
{
|
||||
release.Link = new Uri(release.Link.ToString().Substring(downloadUrlBase.Length), UriKind.Relative);
|
||||
}
|
||||
}
|
||||
|
||||
return releases;
|
||||
}
|
||||
|
||||
public virtual Uri UncleanLink(Uri link)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(downloadUrlBase))
|
||||
{
|
||||
return link;
|
||||
}
|
||||
|
||||
if (link.ToString().StartsWith(downloadUrlBase))
|
||||
{
|
||||
return link;
|
||||
}
|
||||
|
||||
return new Uri(downloadUrlBase + link.ToString(), UriKind.RelativeOrAbsolute);
|
||||
}
|
||||
|
||||
protected ICollection<int> MapTrackerCatToNewznab(string input)
|
||||
{
|
||||
var cats = new List<int>();
|
||||
if (null != input)
|
||||
{
|
||||
var mapping = categoryMapping.Where(m => m.TrackerCategory != null && m.TrackerCategory.ToLowerInvariant() == input.ToLowerInvariant()).FirstOrDefault();
|
||||
if (mapping != null)
|
||||
{
|
||||
cats.Add(mapping.NewzNabCategory);
|
||||
}
|
||||
|
||||
// 1:1 category mapping
|
||||
try
|
||||
{
|
||||
var trackerCategoryInt = int.Parse(input);
|
||||
cats.Add(trackerCategoryInt + 100000);
|
||||
}
|
||||
catch (FormatException)
|
||||
{
|
||||
// input is not an integer, continue
|
||||
}
|
||||
}
|
||||
return cats;
|
||||
}
|
||||
|
||||
protected ICollection<int> MapTrackerCatDescToNewznab(string input)
|
||||
{
|
||||
var cats = new List<int>();
|
||||
if (null != input)
|
||||
{
|
||||
var mapping = categoryMapping.Where(m => m.TrackerCategoryDesc != null && m.TrackerCategoryDesc.ToLowerInvariant() == input.ToLowerInvariant()).FirstOrDefault();
|
||||
if (mapping != null)
|
||||
{
|
||||
cats.Add(mapping.NewzNabCategory);
|
||||
|
||||
if (mapping.TrackerCategory != null)
|
||||
{
|
||||
// 1:1 category mapping
|
||||
try
|
||||
{
|
||||
var trackerCategoryInt = int.Parse(mapping.TrackerCategory);
|
||||
cats.Add(trackerCategoryInt + 100000);
|
||||
}
|
||||
catch (FormatException)
|
||||
{
|
||||
// mapping.TrackerCategory is not an integer, continue
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return cats;
|
||||
}
|
||||
|
||||
public static string GetIndexerID(Type type)
|
||||
{
|
||||
return StringUtil.StripNonAlphaNumeric(type.Name.ToLowerInvariant());
|
||||
if (configData != null)
|
||||
LoadValuesFromJson(null);
|
||||
}
|
||||
|
||||
public virtual Task<ConfigurationData> GetConfigurationForSetup()
|
||||
@@ -191,109 +91,9 @@ namespace Jackett.Indexers
|
||||
|
||||
public virtual void SaveConfig()
|
||||
{
|
||||
indexerService.SaveConfig(this as IIndexer, configData.ToJson(protectionService, forDisplay: false));
|
||||
configurationService.Save(this as IIndexer, configData.ToJson(protectionService, forDisplay: false));
|
||||
}
|
||||
|
||||
protected void OnParseError(string results, Exception ex)
|
||||
{
|
||||
var fileName = string.Format("Error on {0} for {1}.txt", DateTime.Now.ToString("yyyyMMddHHmmss"), DisplayName);
|
||||
var spacing = string.Join("", Enumerable.Repeat(Environment.NewLine, 5));
|
||||
var fileContents = string.Format("{0}{1}{2}", ex, spacing, results);
|
||||
logger.Error(fileName + fileContents);
|
||||
throw ex;
|
||||
}
|
||||
|
||||
protected void CleanCache()
|
||||
{
|
||||
foreach (var expired in cache.Where(i => DateTime.Now - i.Created > cacheTime).ToList())
|
||||
{
|
||||
cache.Remove(expired);
|
||||
}
|
||||
}
|
||||
|
||||
protected async Task FollowIfRedirect(WebClientStringResult response, string referrer = null, string overrideRedirectUrl = null, string overrideCookies = null, bool accumulateCookies = false)
|
||||
{
|
||||
var byteResult = new WebClientByteResult();
|
||||
// Map to byte
|
||||
Mapper.Map(response, byteResult);
|
||||
await FollowIfRedirect(byteResult, referrer, overrideRedirectUrl, overrideCookies, accumulateCookies);
|
||||
// Map to string
|
||||
Mapper.Map(byteResult, response);
|
||||
}
|
||||
|
||||
protected async Task FollowIfRedirect(WebClientByteResult response, string referrer = null, string overrideRedirectUrl = null, string overrideCookies = null, bool accumulateCookies = false)
|
||||
{
|
||||
// Follow up to 5 redirects
|
||||
for (int i = 0; i < 5; i++)
|
||||
{
|
||||
if (!response.IsRedirect)
|
||||
break;
|
||||
await DoFollowIfRedirect(response, referrer, overrideRedirectUrl, overrideCookies, accumulateCookies);
|
||||
if (accumulateCookies)
|
||||
{
|
||||
CookieHeader = ResolveCookies((CookieHeader != null && CookieHeader != ""? CookieHeader + " " : "") + (overrideCookies != null && overrideCookies != "" ? overrideCookies + " " : "") + response.Cookies);
|
||||
overrideCookies = response.Cookies = CookieHeader;
|
||||
}
|
||||
if (overrideCookies != null && response.Cookies == null)
|
||||
{
|
||||
response.Cookies = overrideCookies;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private String ResolveCookies(String incomingCookies = "")
|
||||
{
|
||||
var redirRequestCookies = (CookieHeader != null && CookieHeader != "" ? CookieHeader + " " : "") + incomingCookies;
|
||||
System.Text.RegularExpressions.Regex expression = new System.Text.RegularExpressions.Regex(@"([^\\,;\s]+)=([^=\\,;\s]*)");
|
||||
Dictionary<string, string> cookieDIctionary = new Dictionary<string, string>();
|
||||
var matches = expression.Match(redirRequestCookies);
|
||||
while (matches.Success)
|
||||
{
|
||||
if (matches.Groups.Count > 2) cookieDIctionary[matches.Groups[1].Value] = matches.Groups[2].Value;
|
||||
matches = matches.NextMatch();
|
||||
}
|
||||
return string.Join("; ", cookieDIctionary.Select(kv => kv.Key.ToString() + "=" + kv.Value.ToString()).ToArray());
|
||||
|
||||
}
|
||||
|
||||
// Update CookieHeader with new cookies and save the config if something changed (e.g. a new CloudFlare clearance cookie was issued)
|
||||
protected void UpdateCookieHeader(string newCookies, string cookieOverride = null)
|
||||
{
|
||||
string newCookieHeader = ResolveCookies((cookieOverride != null && cookieOverride != "" ? cookieOverride + " " : "") + newCookies);
|
||||
if (CookieHeader != newCookieHeader)
|
||||
{
|
||||
logger.Debug(string.Format("updating Cookies {0} => {1}", CookieHeader, newCookieHeader));
|
||||
CookieHeader = newCookieHeader;
|
||||
if (IsConfigured)
|
||||
SaveConfig();
|
||||
}
|
||||
}
|
||||
|
||||
private async Task DoFollowIfRedirect(WebClientByteResult incomingResponse, string referrer = null, string overrideRedirectUrl = null, string overrideCookies = null, bool accumulateCookies = false)
|
||||
{
|
||||
if (incomingResponse.IsRedirect)
|
||||
{
|
||||
var redirRequestCookies = "";
|
||||
if (accumulateCookies)
|
||||
{
|
||||
redirRequestCookies = ResolveCookies((CookieHeader != "" ? CookieHeader + " " : "") + (overrideCookies != null ? overrideCookies : ""));
|
||||
} else
|
||||
{
|
||||
redirRequestCookies = (overrideCookies != null ? overrideCookies : "");
|
||||
}
|
||||
// Do redirect
|
||||
var redirectedResponse = await webclient.GetBytes(new WebRequest()
|
||||
{
|
||||
Url = overrideRedirectUrl ?? incomingResponse.RedirectingTo,
|
||||
Referer = referrer,
|
||||
Cookies = redirRequestCookies,
|
||||
Encoding = Encoding
|
||||
});
|
||||
Mapper.Map(redirectedResponse, incomingResponse);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
protected void LoadLegacyCookieConfig(JToken jsonConfig)
|
||||
{
|
||||
string legacyCookieHeader = (string)jsonConfig["cookie_header"];
|
||||
@@ -334,19 +134,15 @@ namespace Jackett.Indexers
|
||||
{
|
||||
configData.SiteLink.Value = DefaultSiteLink;
|
||||
}
|
||||
if (!configData.SiteLink.Value.EndsWith("/"))
|
||||
if (!configData.SiteLink.Value.EndsWith("/", StringComparison.Ordinal))
|
||||
configData.SiteLink.Value += "/";
|
||||
|
||||
var match = Regex.Match(configData.SiteLink.Value, "^https?:\\/\\/[\\w\\-\\/\\.]+$");
|
||||
if (!match.Success)
|
||||
{
|
||||
throw new Exception(string.Format("\"{0}\" is not a valid URL.", configData.SiteLink.Value));
|
||||
}
|
||||
|
||||
// check whether the site link is well-formatted
|
||||
var siteUri = new Uri(configData.SiteLink.Value);
|
||||
SiteLink = configData.SiteLink.Value;
|
||||
}
|
||||
|
||||
public virtual void LoadFromSavedConfiguration(JToken jsonConfig)
|
||||
public void LoadFromSavedConfiguration(JToken jsonConfig)
|
||||
{
|
||||
if (jsonConfig is JArray)
|
||||
{
|
||||
@@ -362,12 +158,115 @@ namespace Jackett.Indexers
|
||||
}
|
||||
}
|
||||
|
||||
public async virtual Task<byte[]> Download(Uri link)
|
||||
protected async Task ConfigureIfOK(string cookies, bool isLoggedin, Func<Task> onError)
|
||||
{
|
||||
return await Download(link, RequestType.GET);
|
||||
if (isLoggedin)
|
||||
{
|
||||
CookieHeader = cookies;
|
||||
IsConfigured = true;
|
||||
SaveConfig();
|
||||
}
|
||||
else
|
||||
{
|
||||
await onError();
|
||||
}
|
||||
}
|
||||
|
||||
public async virtual Task<byte[]> Download(Uri link, RequestType method = RequestType.GET)
|
||||
protected virtual IEnumerable<ReleaseInfo> FilterResults(TorznabQuery query, IEnumerable<ReleaseInfo> results)
|
||||
{
|
||||
if (query.Categories.Length == 0)
|
||||
return results;
|
||||
|
||||
var filteredResults = results.Where(result =>
|
||||
{
|
||||
return result.Category.IsEmptyOrNull() || query.Categories.Intersect(result.Category).Any() || TorznabCatType.QueryContainsParentCategory(query.Categories, result.Category);
|
||||
});
|
||||
|
||||
return filteredResults;
|
||||
}
|
||||
|
||||
public bool CanHandleQuery(TorznabQuery query)
|
||||
{
|
||||
if (query == null)
|
||||
return false;
|
||||
if (query.QueryType == "caps")
|
||||
return true;
|
||||
|
||||
var caps = TorznabCaps;
|
||||
|
||||
if (query.HasSpecifiedCategories)
|
||||
if (!caps.SupportsCategories(query.Categories))
|
||||
return false;
|
||||
|
||||
if (caps.SearchAvailable && query.IsSearch)
|
||||
return true;
|
||||
if (caps.TVSearchAvailable && query.IsTVSearch)
|
||||
return true;
|
||||
if (caps.MovieSearchAvailable && query.IsMovieSearch)
|
||||
return true;
|
||||
if (caps.SupportsTVRageSearch && query.IsTVRageSearch)
|
||||
return true;
|
||||
if (caps.SupportsImdbSearch && query.IsImdbQuery)
|
||||
return true;
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public void Unconfigure()
|
||||
{
|
||||
IsConfigured = false;
|
||||
}
|
||||
|
||||
public abstract Task<IndexerConfigurationStatus> ApplyConfiguration(JToken configJson);
|
||||
|
||||
public virtual async Task<IEnumerable<ReleaseInfo>> ResultsForQuery(TorznabQuery query)
|
||||
{
|
||||
if (!CanHandleQuery(query))
|
||||
return new ReleaseInfo[0];
|
||||
var results = await PerformQuery(query);
|
||||
results = FilterResults(query, results);
|
||||
results = results.Select(r =>
|
||||
{
|
||||
r.Origin = this;
|
||||
|
||||
// Some trackers do not keep their clocks up to date and can be ~20 minutes out!
|
||||
if (r.PublishDate > DateTime.Now)
|
||||
r.PublishDate = DateTime.Now;
|
||||
return r;
|
||||
});
|
||||
|
||||
return results;
|
||||
}
|
||||
protected abstract Task<IEnumerable<ReleaseInfo>> PerformQuery(TorznabQuery query);
|
||||
}
|
||||
|
||||
public abstract class BaseWebIndexer : BaseIndexer, IWebIndexer
|
||||
{
|
||||
protected BaseWebIndexer(string name, string link, string description, IIndexerConfigurationService configService, IWebClient client, Logger logger, ConfigurationData configData, IProtectionService p, TorznabCapabilities caps = null, string downloadBase = null)
|
||||
: base(name, link, description, configService, logger, configData, p)
|
||||
{
|
||||
this.webclient = client;
|
||||
this.downloadUrlBase = downloadBase;
|
||||
|
||||
if (caps == null)
|
||||
caps = TorznabUtil.CreateDefaultTorznabTVCaps();
|
||||
TorznabCaps = caps;
|
||||
}
|
||||
|
||||
// minimal constructor used by e.g. cardigann generic indexer
|
||||
protected BaseWebIndexer(IIndexerConfigurationService configService, IWebClient client, Logger logger, IProtectionService p)
|
||||
: base("", "/", "", configService, logger, null, p)
|
||||
{
|
||||
this.webclient = client;
|
||||
}
|
||||
|
||||
public async virtual Task<byte[]> Download(Uri link)
|
||||
{
|
||||
var uncleanLink = UncleanLink(link);
|
||||
return await Download(uncleanLink, RequestType.GET);
|
||||
}
|
||||
|
||||
protected async Task<byte[]> Download(Uri link, RequestType method)
|
||||
{
|
||||
// do some extra escaping, needed for HD-Torrents
|
||||
var requestLink = link.ToString()
|
||||
@@ -380,7 +279,7 @@ namespace Jackett.Indexers
|
||||
logger.Error("Failed download cookies: " + this.CookieHeader);
|
||||
if (response.Content != null)
|
||||
logger.Error("Failed download response:\n" + Encoding.UTF8.GetString(response.Content));
|
||||
throw new Exception($"Remote server returned {response.Status.ToString()}" + (response.IsRedirect ? " => "+response.RedirectingTo : ""));
|
||||
throw new Exception($"Remote server returned {response.Status.ToString()}" + (response.IsRedirect ? " => " + response.RedirectingTo : ""));
|
||||
}
|
||||
|
||||
return response.Content;
|
||||
@@ -531,32 +430,90 @@ namespace Jackett.Indexers
|
||||
{
|
||||
response.Cookies = ResolveCookies(firstCallCookies + (accumulateCookies ? " " + response.Cookies : ""));
|
||||
}
|
||||
|
||||
|
||||
return response;
|
||||
}
|
||||
|
||||
protected async Task ConfigureIfOK(string cookies, bool isLoggedin, Func<Task> onError)
|
||||
protected async Task FollowIfRedirect(WebClientStringResult response, string referrer = null, string overrideRedirectUrl = null, string overrideCookies = null, bool accumulateCookies = false)
|
||||
{
|
||||
if (isLoggedin)
|
||||
var byteResult = new WebClientByteResult();
|
||||
// Map to byte
|
||||
Mapper.Map(response, byteResult);
|
||||
await FollowIfRedirect(byteResult, referrer, overrideRedirectUrl, overrideCookies, accumulateCookies);
|
||||
// Map to string
|
||||
Mapper.Map(byteResult, response);
|
||||
}
|
||||
|
||||
protected async Task FollowIfRedirect(WebClientByteResult response, string referrer = null, string overrideRedirectUrl = null, string overrideCookies = null, bool accumulateCookies = false)
|
||||
{
|
||||
// Follow up to 5 redirects
|
||||
for (int i = 0; i < 5; i++)
|
||||
{
|
||||
CookieHeader = cookies;
|
||||
IsConfigured = true;
|
||||
SaveConfig();
|
||||
}
|
||||
else
|
||||
{
|
||||
await onError();
|
||||
if (!response.IsRedirect)
|
||||
break;
|
||||
await DoFollowIfRedirect(response, referrer, overrideRedirectUrl, overrideCookies, accumulateCookies);
|
||||
if (accumulateCookies)
|
||||
{
|
||||
CookieHeader = ResolveCookies((CookieHeader != null && CookieHeader != "" ? CookieHeader + " " : "") + (overrideCookies != null && overrideCookies != "" ? overrideCookies + " " : "") + response.Cookies);
|
||||
overrideCookies = response.Cookies = CookieHeader;
|
||||
}
|
||||
if (overrideCookies != null && response.Cookies == null)
|
||||
{
|
||||
response.Cookies = overrideCookies;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public virtual IEnumerable<ReleaseInfo> FilterResults(TorznabQuery query, IEnumerable<ReleaseInfo> results)
|
||||
private String ResolveCookies(String incomingCookies = "")
|
||||
{
|
||||
foreach (var result in results)
|
||||
var redirRequestCookies = (CookieHeader != null && CookieHeader != "" ? CookieHeader + " " : "") + incomingCookies;
|
||||
System.Text.RegularExpressions.Regex expression = new System.Text.RegularExpressions.Regex(@"([^\\,;\s]+)=([^=\\,;\s]*)");
|
||||
Dictionary<string, string> cookieDIctionary = new Dictionary<string, string>();
|
||||
var matches = expression.Match(redirRequestCookies);
|
||||
while (matches.Success)
|
||||
{
|
||||
if (query.Categories.Length == 0 || result.Category == null || result.Category.Count() == 0 || query.Categories.Intersect(result.Category).Any() || TorznabCatType.QueryContainsParentCategory(query.Categories, result.Category))
|
||||
if (matches.Groups.Count > 2) cookieDIctionary[matches.Groups[1].Value] = matches.Groups[2].Value;
|
||||
matches = matches.NextMatch();
|
||||
}
|
||||
return string.Join("; ", cookieDIctionary.Select(kv => kv.Key.ToString() + "=" + kv.Value.ToString()).ToArray());
|
||||
|
||||
}
|
||||
|
||||
// Update CookieHeader with new cookies and save the config if something changed (e.g. a new CloudFlare clearance cookie was issued)
|
||||
protected void UpdateCookieHeader(string newCookies, string cookieOverride = null)
|
||||
{
|
||||
string newCookieHeader = ResolveCookies((cookieOverride != null && cookieOverride != "" ? cookieOverride + " " : "") + newCookies);
|
||||
if (CookieHeader != newCookieHeader)
|
||||
{
|
||||
logger.Debug(string.Format("updating Cookies {0} => {1}", CookieHeader, newCookieHeader));
|
||||
CookieHeader = newCookieHeader;
|
||||
if (IsConfigured)
|
||||
SaveConfig();
|
||||
}
|
||||
}
|
||||
|
||||
private async Task DoFollowIfRedirect(WebClientByteResult incomingResponse, string referrer = null, string overrideRedirectUrl = null, string overrideCookies = null, bool accumulateCookies = false)
|
||||
{
|
||||
if (incomingResponse.IsRedirect)
|
||||
{
|
||||
var redirRequestCookies = "";
|
||||
if (accumulateCookies)
|
||||
{
|
||||
yield return result;
|
||||
redirRequestCookies = ResolveCookies((CookieHeader != "" ? CookieHeader + " " : "") + (overrideCookies != null ? overrideCookies : ""));
|
||||
}
|
||||
else
|
||||
{
|
||||
redirRequestCookies = (overrideCookies != null ? overrideCookies : "");
|
||||
}
|
||||
// Do redirect
|
||||
var redirectedResponse = await webclient.GetBytes(new WebRequest()
|
||||
{
|
||||
Url = overrideRedirectUrl ?? incomingResponse.RedirectingTo,
|
||||
Referer = referrer,
|
||||
Cookies = redirRequestCookies,
|
||||
Encoding = Encoding
|
||||
});
|
||||
Mapper.Map(redirectedResponse, incomingResponse);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -642,26 +599,132 @@ namespace Jackett.Indexers
|
||||
return result.Distinct().ToList();
|
||||
}
|
||||
|
||||
public bool CanHandleQuery(TorznabQuery query)
|
||||
protected ICollection<int> MapTrackerCatToNewznab(string input)
|
||||
{
|
||||
if (query == null)
|
||||
return false;
|
||||
var caps = TorznabCaps;
|
||||
if (!caps.SearchAvailable && query.IsSearch)
|
||||
return false;
|
||||
if (!caps.TVSearchAvailable && query.IsTVSearch)
|
||||
return false;
|
||||
if (!caps.MovieSearchAvailable && query.IsMovieSearch)
|
||||
return false;
|
||||
if (!caps.SupportsTVRageSearch && query.IsTVRageSearch)
|
||||
return false;
|
||||
if (!caps.SupportsImdbSearch && query.IsImdbQuery)
|
||||
return false;
|
||||
var cats = new List<int>();
|
||||
if (null != input)
|
||||
{
|
||||
var mapping = categoryMapping.Where(m => m.TrackerCategory != null && m.TrackerCategory.ToLowerInvariant() == input.ToLowerInvariant()).FirstOrDefault();
|
||||
if (mapping != null)
|
||||
{
|
||||
cats.Add(mapping.NewzNabCategory);
|
||||
}
|
||||
|
||||
if (query.HasSpecifiedCategories)
|
||||
if (!caps.SupportsCategories(query.Categories))
|
||||
return false;
|
||||
return true;
|
||||
// 1:1 category mapping
|
||||
try
|
||||
{
|
||||
var trackerCategoryInt = int.Parse(input);
|
||||
cats.Add(trackerCategoryInt + 100000);
|
||||
}
|
||||
catch (FormatException)
|
||||
{
|
||||
// input is not an integer, continue
|
||||
}
|
||||
}
|
||||
return cats;
|
||||
}
|
||||
|
||||
protected ICollection<int> MapTrackerCatDescToNewznab(string input)
|
||||
{
|
||||
var cats = new List<int>();
|
||||
if (null != input)
|
||||
{
|
||||
var mapping = categoryMapping.Where(m => m.TrackerCategoryDesc != null && m.TrackerCategoryDesc.ToLowerInvariant() == input.ToLowerInvariant()).FirstOrDefault();
|
||||
if (mapping != null)
|
||||
{
|
||||
cats.Add(mapping.NewzNabCategory);
|
||||
|
||||
if (mapping.TrackerCategory != null)
|
||||
{
|
||||
// 1:1 category mapping
|
||||
try
|
||||
{
|
||||
var trackerCategoryInt = int.Parse(mapping.TrackerCategory);
|
||||
cats.Add(trackerCategoryInt + 100000);
|
||||
}
|
||||
catch (FormatException)
|
||||
{
|
||||
// mapping.TrackerCategory is not an integer, continue
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return cats;
|
||||
}
|
||||
|
||||
private IEnumerable<ReleaseInfo> CleanLinks(IEnumerable<ReleaseInfo> releases)
|
||||
{
|
||||
if (string.IsNullOrEmpty(downloadUrlBase))
|
||||
return releases;
|
||||
foreach (var release in releases)
|
||||
{
|
||||
if (release.Link.ToString().StartsWith(downloadUrlBase, StringComparison.Ordinal))
|
||||
{
|
||||
release.Link = new Uri(release.Link.ToString().Substring(downloadUrlBase.Length), UriKind.Relative);
|
||||
}
|
||||
}
|
||||
|
||||
return releases;
|
||||
}
|
||||
|
||||
public override async Task<IEnumerable<ReleaseInfo>> ResultsForQuery(TorznabQuery query)
|
||||
{
|
||||
var results = await base.ResultsForQuery(query);
|
||||
results = CleanLinks(results);
|
||||
|
||||
return results;
|
||||
}
|
||||
|
||||
protected virtual Uri UncleanLink(Uri link)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(downloadUrlBase))
|
||||
{
|
||||
return link;
|
||||
}
|
||||
|
||||
if (link.ToString().StartsWith(downloadUrlBase, StringComparison.Ordinal))
|
||||
{
|
||||
return link;
|
||||
}
|
||||
|
||||
return new Uri(downloadUrlBase + link.ToString(), UriKind.RelativeOrAbsolute);
|
||||
}
|
||||
|
||||
protected void OnParseError(string results, Exception ex)
|
||||
{
|
||||
var fileName = string.Format("Error on {0} for {1}.txt", DateTime.Now.ToString("yyyyMMddHHmmss"), DisplayName);
|
||||
var spacing = string.Join("", Enumerable.Repeat(Environment.NewLine, 5));
|
||||
var fileContents = string.Format("{0}{1}{2}", ex, spacing, results);
|
||||
logger.Error(fileName + fileContents);
|
||||
throw ex;
|
||||
}
|
||||
|
||||
public override TorznabCapabilities TorznabCaps { get; protected set; }
|
||||
|
||||
[JsonConverter(typeof(EncodingJsonConverter))]
|
||||
public Encoding Encoding { get; protected set; }
|
||||
|
||||
private List<CategoryMapping> categoryMapping = new List<CategoryMapping>();
|
||||
protected IWebClient webclient;
|
||||
protected readonly string downloadUrlBase = "";
|
||||
}
|
||||
|
||||
public abstract class BaseCachingWebIndexer : BaseWebIndexer
|
||||
{
|
||||
protected BaseCachingWebIndexer(string name, string link, string description, IIndexerConfigurationService configService, IWebClient client, Logger logger, ConfigurationData configData, IProtectionService p, TorznabCapabilities caps = null, string downloadBase = null)
|
||||
: base(name, link, description, configService, client, logger, configData, p, caps, downloadBase)
|
||||
{
|
||||
}
|
||||
|
||||
protected void CleanCache()
|
||||
{
|
||||
foreach (var expired in cache.Where(i => DateTime.Now - i.Created > cacheTime).ToList())
|
||||
{
|
||||
cache.Remove(expired);
|
||||
}
|
||||
}
|
||||
|
||||
protected static List<CachedQueryResult> cache = new List<CachedQueryResult>();
|
||||
protected static readonly TimeSpan cacheTime = new TimeSpan(0, 9, 0);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -15,7 +15,7 @@ using System.Text;
|
||||
|
||||
namespace Jackett.Indexers
|
||||
{
|
||||
public class BestFriends : BaseIndexer, IIndexer
|
||||
public class BestFriends : BaseWebIndexer
|
||||
{
|
||||
string LoginUrl { get { return SiteLink + "login.php"; } }
|
||||
string TakeLoginUrl { get { return SiteLink + "takelogin.php"; } }
|
||||
@@ -27,12 +27,12 @@ namespace Jackett.Indexers
|
||||
set { base.configData = value; }
|
||||
}
|
||||
|
||||
public BestFriends(IIndexerManagerService i, IWebClient wc, Logger l, IProtectionService ps)
|
||||
public BestFriends(IIndexerConfigurationService configService, IWebClient wc, Logger l, IProtectionService ps)
|
||||
: base(name: "Best Friends",
|
||||
description: "A German general tracker.",
|
||||
link: "http://bf.mine.nu/",
|
||||
caps: TorznabUtil.CreateDefaultTorznabTVCaps(),
|
||||
manager: i,
|
||||
configService: configService,
|
||||
client: wc,
|
||||
logger: l,
|
||||
p: ps,
|
||||
@@ -92,7 +92,7 @@ namespace Jackett.Indexers
|
||||
return configData;
|
||||
}
|
||||
|
||||
public async Task<IndexerConfigurationStatus> ApplyConfiguration(JToken configJson)
|
||||
public override async Task<IndexerConfigurationStatus> ApplyConfiguration(JToken configJson)
|
||||
{
|
||||
LoadValuesFromJson(configJson);
|
||||
|
||||
@@ -127,7 +127,7 @@ namespace Jackett.Indexers
|
||||
return IndexerConfigurationStatus.RequiresTesting;
|
||||
}
|
||||
|
||||
public async Task<IEnumerable<ReleaseInfo>> PerformQuery(TorznabQuery query)
|
||||
protected override async Task<IEnumerable<ReleaseInfo>> PerformQuery(TorznabQuery query)
|
||||
{
|
||||
TimeZoneInfo.TransitionTime startTransition = TimeZoneInfo.TransitionTime.CreateFloatingDateRule(new DateTime(1, 1, 1, 3, 0, 0), 3, 5, DayOfWeek.Sunday);
|
||||
TimeZoneInfo.TransitionTime endTransition = TimeZoneInfo.TransitionTime.CreateFloatingDateRule(new DateTime(1, 1, 1, 4, 0, 0), 10, 5, DayOfWeek.Sunday);
|
||||
|
||||
@@ -16,7 +16,7 @@ using System.Text;
|
||||
|
||||
namespace Jackett.Indexers
|
||||
{
|
||||
public class BeyondHD : BaseIndexer, IIndexer
|
||||
public class BeyondHD : BaseWebIndexer
|
||||
{
|
||||
private string SearchUrl { get { return SiteLink + "browse.php?searchin=title&incldead=0&"; } }
|
||||
|
||||
@@ -26,12 +26,12 @@ namespace Jackett.Indexers
|
||||
set { base.configData = value; }
|
||||
}
|
||||
|
||||
public BeyondHD(IIndexerManagerService i, Logger l, IWebClient w, IProtectionService ps)
|
||||
public BeyondHD(IIndexerConfigurationService configService, IWebClient w, Logger l, IProtectionService ps)
|
||||
: base(name: "BeyondHD",
|
||||
description: "Without BeyondHD, your HDTV is just a TV",
|
||||
link: "https://beyond-hd.me/",
|
||||
caps: new TorznabCapabilities(),
|
||||
manager: i,
|
||||
configService: configService,
|
||||
client: w,
|
||||
logger: l,
|
||||
p: ps,
|
||||
@@ -85,7 +85,7 @@ namespace Jackett.Indexers
|
||||
|
||||
}
|
||||
|
||||
public async Task<IndexerConfigurationStatus> ApplyConfiguration(JToken configJson)
|
||||
public override async Task<IndexerConfigurationStatus> ApplyConfiguration(JToken configJson)
|
||||
{
|
||||
LoadValuesFromJson(configJson);
|
||||
|
||||
@@ -98,7 +98,7 @@ namespace Jackett.Indexers
|
||||
return IndexerConfigurationStatus.RequiresTesting;
|
||||
}
|
||||
|
||||
public async Task<IEnumerable<ReleaseInfo>> PerformQuery(TorznabQuery query)
|
||||
protected override async Task<IEnumerable<ReleaseInfo>> PerformQuery(TorznabQuery query)
|
||||
{
|
||||
List<ReleaseInfo> releases = new List<ReleaseInfo>();
|
||||
|
||||
|
||||
@@ -16,7 +16,7 @@ using System.Collections.Specialized;
|
||||
|
||||
namespace Jackett.Indexers
|
||||
{
|
||||
public class BitCityReloaded : BaseIndexer, IIndexer
|
||||
public class BitCityReloaded : BaseWebIndexer
|
||||
{
|
||||
string LoginUrl { get { return SiteLink + "login.php"; } }
|
||||
string BrowseUrl { get { return SiteLink + "uebersicht.php"; } }
|
||||
@@ -28,12 +28,12 @@ namespace Jackett.Indexers
|
||||
set { base.configData = value; }
|
||||
}
|
||||
|
||||
public BitCityReloaded(IIndexerManagerService i, IWebClient wc, Logger l, IProtectionService ps)
|
||||
public BitCityReloaded(IIndexerConfigurationService configService, IWebClient wc, Logger l, IProtectionService ps)
|
||||
: base(name: "Bit-City Reloaded",
|
||||
description: "A German general tracker.",
|
||||
link: "https://bc-reloaded.net/",
|
||||
caps: TorznabUtil.CreateDefaultTorznabTVCaps(),
|
||||
manager: i,
|
||||
configService: configService,
|
||||
client: wc,
|
||||
logger: l,
|
||||
p: ps,
|
||||
@@ -83,7 +83,7 @@ namespace Jackett.Indexers
|
||||
AddCategoryMapping(25, TorznabCatType.TVSport); // Sport
|
||||
}
|
||||
|
||||
public async Task<IndexerConfigurationStatus> ApplyConfiguration(JToken configJson)
|
||||
public override async Task<IndexerConfigurationStatus> ApplyConfiguration(JToken configJson)
|
||||
{
|
||||
LoadValuesFromJson(configJson);
|
||||
|
||||
@@ -103,7 +103,7 @@ namespace Jackett.Indexers
|
||||
return IndexerConfigurationStatus.RequiresTesting;
|
||||
}
|
||||
|
||||
public async Task<IEnumerable<ReleaseInfo>> PerformQuery(TorznabQuery query)
|
||||
protected override async Task<IEnumerable<ReleaseInfo>> PerformQuery(TorznabQuery query)
|
||||
{
|
||||
var releases = new List<ReleaseInfo>();
|
||||
|
||||
|
||||
@@ -19,7 +19,7 @@ using System.Collections.Specialized;
|
||||
|
||||
namespace Jackett.Indexers
|
||||
{
|
||||
public class BitHdtv : BaseIndexer, IIndexer
|
||||
public class BitHdtv : BaseWebIndexer
|
||||
{
|
||||
private string LoginUrl { get { return SiteLink + "login.php"; } }
|
||||
private string TakeLoginUrl { get { return SiteLink + "takelogin.php"; } }
|
||||
@@ -32,12 +32,12 @@ namespace Jackett.Indexers
|
||||
set { base.configData = value; }
|
||||
}
|
||||
|
||||
public BitHdtv(IIndexerManagerService i, Logger l, IWebClient w, IProtectionService ps)
|
||||
public BitHdtv(IIndexerConfigurationService configService, IWebClient w, Logger l, IProtectionService ps)
|
||||
: base(name: "BIT-HDTV",
|
||||
description: "Home of high definition invites",
|
||||
link: "https://www.bit-hdtv.com/",
|
||||
caps: new TorznabCapabilities(),
|
||||
manager: i,
|
||||
configService: configService,
|
||||
client: w,
|
||||
logger: l,
|
||||
p: ps,
|
||||
@@ -71,7 +71,7 @@ namespace Jackett.Indexers
|
||||
return result;
|
||||
}
|
||||
|
||||
public async Task<IndexerConfigurationStatus> ApplyConfiguration(JToken configJson)
|
||||
public override async Task<IndexerConfigurationStatus> ApplyConfiguration(JToken configJson)
|
||||
{
|
||||
LoadValuesFromJson(configJson);
|
||||
|
||||
@@ -117,7 +117,7 @@ namespace Jackett.Indexers
|
||||
return IndexerConfigurationStatus.RequiresTesting;
|
||||
}
|
||||
|
||||
public async Task<IEnumerable<ReleaseInfo>> PerformQuery(TorznabQuery query)
|
||||
protected override async Task<IEnumerable<ReleaseInfo>> PerformQuery(TorznabQuery query)
|
||||
{
|
||||
var releases = new List<ReleaseInfo>();
|
||||
var searchString = query.GetQueryString();
|
||||
@@ -170,7 +170,7 @@ namespace Jackett.Indexers
|
||||
continue;
|
||||
|
||||
var dateString = qRow.Children().ElementAt(5).Cq().Text().Trim();
|
||||
var pubDate = DateTime.ParseExact(dateString, "yyyy-MM-dd HH:mm:ss", CultureInfo.InvariantCulture);
|
||||
var pubDate = DateTime.ParseExact(dateString, "yyyy-MM-ddHH:mm:ss", CultureInfo.InvariantCulture);
|
||||
release.PublishDate = DateTime.SpecifyKind(pubDate, DateTimeKind.Local);
|
||||
|
||||
var sizeStr = qRow.Children().ElementAt(6).Cq().Text();
|
||||
|
||||
@@ -19,7 +19,7 @@ using System.Web;
|
||||
|
||||
namespace Jackett.Indexers
|
||||
{
|
||||
public class BitMeTV : BaseIndexer, IIndexer
|
||||
public class BitMeTV : BaseWebIndexer
|
||||
{
|
||||
//https is poorly implemented on BitMeTV. Site uses http to login, but then redirects to https for search
|
||||
private string LoginUrl { get { return SiteLink + "login.php"; } }
|
||||
@@ -33,12 +33,12 @@ namespace Jackett.Indexers
|
||||
set { base.configData = value; }
|
||||
}
|
||||
|
||||
public BitMeTV(IIndexerManagerService i, Logger l, IWebClient c, IProtectionService ps)
|
||||
public BitMeTV(IIndexerConfigurationService configService, IWebClient c, Logger l, IProtectionService ps)
|
||||
: base(name: "BitMeTV",
|
||||
description: "TV Episode specialty tracker",
|
||||
link: "http://www.bitmetv.org/",
|
||||
caps: TorznabUtil.CreateDefaultTorznabTVCaps(),
|
||||
manager: i,
|
||||
configService: configService,
|
||||
client: c,
|
||||
logger: l,
|
||||
p: ps,
|
||||
@@ -62,7 +62,7 @@ namespace Jackett.Indexers
|
||||
return configData;
|
||||
}
|
||||
|
||||
public async Task<IndexerConfigurationStatus> ApplyConfiguration(JToken configJson)
|
||||
public override async Task<IndexerConfigurationStatus> ApplyConfiguration(JToken configJson)
|
||||
{
|
||||
LoadValuesFromJson(configJson);
|
||||
|
||||
@@ -87,7 +87,7 @@ namespace Jackett.Indexers
|
||||
return IndexerConfigurationStatus.RequiresTesting;
|
||||
}
|
||||
|
||||
public async Task<IEnumerable<ReleaseInfo>> PerformQuery(TorznabQuery query)
|
||||
protected override async Task<IEnumerable<ReleaseInfo>> PerformQuery(TorznabQuery query)
|
||||
{
|
||||
var releases = new List<ReleaseInfo>();
|
||||
var episodeSearchUrl = string.Format("{0}?search={1}&cat=0&incldead=1", SearchUrl, HttpUtility.UrlEncode(query.GetQueryString()));
|
||||
|
||||
@@ -20,7 +20,7 @@ using System.Text.RegularExpressions;
|
||||
|
||||
namespace Jackett.Indexers
|
||||
{
|
||||
public class BitSoup : BaseIndexer, IIndexer
|
||||
public class BitSoup : BaseWebIndexer
|
||||
{
|
||||
private string BrowseUrl { get { return SiteLink + "browse.php"; } }
|
||||
private string LoginUrl { get { return SiteLink + "takelogin.php"; } }
|
||||
@@ -33,12 +33,12 @@ namespace Jackett.Indexers
|
||||
set { base.configData = value; }
|
||||
}
|
||||
|
||||
public BitSoup(IIndexerManagerService i, IWebClient wc, Logger l, IProtectionService ps)
|
||||
public BitSoup(IIndexerConfigurationService configService, IWebClient wc, Logger l, IProtectionService ps)
|
||||
: base(name: "BitSoup",
|
||||
description: "SoupieBits",
|
||||
link: "https://www.bitsoup.me/",
|
||||
caps: TorznabUtil.CreateDefaultTorznabTVCaps(),
|
||||
manager: i,
|
||||
configService: configService,
|
||||
client: wc,
|
||||
logger: l,
|
||||
p: ps,
|
||||
@@ -142,7 +142,7 @@ namespace Jackett.Indexers
|
||||
//AddCategoryMapping("1", TorznabCatType.BooksForeign);
|
||||
}
|
||||
|
||||
public async Task<IndexerConfigurationStatus> ApplyConfiguration(JToken configJson)
|
||||
public override async Task<IndexerConfigurationStatus> ApplyConfiguration(JToken configJson)
|
||||
{
|
||||
LoadValuesFromJson(configJson);
|
||||
var pairs = new Dictionary<string, string> {
|
||||
@@ -164,7 +164,7 @@ namespace Jackett.Indexers
|
||||
return IndexerConfigurationStatus.RequiresTesting;
|
||||
}
|
||||
|
||||
public async Task<IEnumerable<ReleaseInfo>> PerformQuery(TorznabQuery query)
|
||||
protected override async Task<IEnumerable<ReleaseInfo>> PerformQuery(TorznabQuery query)
|
||||
{
|
||||
var releases = new List<ReleaseInfo>();
|
||||
var searchString = query.GetQueryString();
|
||||
|
||||
@@ -20,7 +20,7 @@ using Newtonsoft.Json;
|
||||
|
||||
namespace Jackett.Indexers
|
||||
{
|
||||
public class BroadcastTheNet : BaseIndexer, IIndexer
|
||||
public class BroadcastTheNet : BaseWebIndexer
|
||||
{
|
||||
string APIBASE = "https://api.broadcasthe.net";
|
||||
|
||||
@@ -30,12 +30,12 @@ namespace Jackett.Indexers
|
||||
set { base.configData = value; }
|
||||
}
|
||||
|
||||
public BroadcastTheNet(IIndexerManagerService i, IWebClient wc, Logger l, IProtectionService ps)
|
||||
public BroadcastTheNet(IIndexerConfigurationService configService, IWebClient wc, Logger l, IProtectionService ps)
|
||||
: base(name: "BroadcastTheNet",
|
||||
description: "Needs no description..",
|
||||
link: "https://broadcasthe.net/",
|
||||
caps: TorznabUtil.CreateDefaultTorznabTVCaps(),
|
||||
manager: i,
|
||||
configService: configService,
|
||||
client: wc,
|
||||
logger: l,
|
||||
p: ps,
|
||||
@@ -46,7 +46,7 @@ namespace Jackett.Indexers
|
||||
Type = "private";
|
||||
}
|
||||
|
||||
public async Task<IndexerConfigurationStatus> ApplyConfiguration(JToken configJson)
|
||||
public override async Task<IndexerConfigurationStatus> ApplyConfiguration(JToken configJson)
|
||||
{
|
||||
LoadValuesFromJson(configJson);
|
||||
|
||||
@@ -78,7 +78,7 @@ namespace Jackett.Indexers
|
||||
return request.ToString();
|
||||
}
|
||||
|
||||
public async Task<IEnumerable<ReleaseInfo>> PerformQuery(TorznabQuery query)
|
||||
protected override async Task<IEnumerable<ReleaseInfo>> PerformQuery(TorznabQuery query)
|
||||
{
|
||||
var searchString = query.GetQueryString();
|
||||
var releases = new List<ReleaseInfo>();
|
||||
|
||||
@@ -6,13 +6,13 @@ using Jackett.Indexers.Abstract;
|
||||
|
||||
namespace Jackett.Indexers
|
||||
{
|
||||
public class BrokenStones : GazelleTracker, IIndexer
|
||||
public class BrokenStones : GazelleTracker
|
||||
{
|
||||
public BrokenStones(IIndexerManagerService indexerManager, IWebClient webClient, Logger logger, IProtectionService protectionService)
|
||||
public BrokenStones(IIndexerConfigurationService configService, IWebClient webClient, Logger logger, IProtectionService protectionService)
|
||||
: base(name: "BrokenStones",
|
||||
desc: null,
|
||||
link: "https://brokenstones.club/",
|
||||
indexerManager: indexerManager,
|
||||
configService: configService,
|
||||
logger: logger,
|
||||
protectionService: protectionService,
|
||||
webClient: webClient
|
||||
|
||||
@@ -10,8 +10,6 @@ using System;
|
||||
using Jackett.Models.IndexerConfig;
|
||||
using System.Collections.Specialized;
|
||||
using System.Text;
|
||||
using YamlDotNet.Serialization;
|
||||
using YamlDotNet.Serialization.NamingConventions;
|
||||
using static Jackett.Models.IndexerConfig.ConfigurationData;
|
||||
using AngleSharp.Parser.Html;
|
||||
using System.Text.RegularExpressions;
|
||||
@@ -22,11 +20,10 @@ using System.Linq;
|
||||
|
||||
namespace Jackett.Indexers
|
||||
{
|
||||
public class CardigannIndexer : BaseIndexer, IIndexer
|
||||
public class CardigannIndexer : BaseWebIndexer
|
||||
{
|
||||
public string DefinitionString { get; protected set; }
|
||||
protected IndexerDefinition Definition;
|
||||
public new string ID { get { return (Definition != null ? Definition.Site : GetIndexerID(GetType())); } }
|
||||
public override string ID { get { return (Definition != null ? Definition.Site : GetIndexerID(GetType())); } }
|
||||
|
||||
protected WebClientStringResult landingResult;
|
||||
protected IHtmlDocument landingResultDocument;
|
||||
@@ -37,228 +34,19 @@ namespace Jackett.Indexers
|
||||
set { base.configData = value; }
|
||||
}
|
||||
|
||||
// A Dictionary allowing the same key multiple times
|
||||
public class KeyValuePairList : List<KeyValuePair<string, selectorBlock>>, IDictionary<string, selectorBlock>
|
||||
{
|
||||
public selectorBlock this[string key]
|
||||
{
|
||||
get
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
set
|
||||
{
|
||||
base.Add(new KeyValuePair<string, selectorBlock>(key, value));
|
||||
}
|
||||
}
|
||||
|
||||
public ICollection<string> Keys
|
||||
{
|
||||
get
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
}
|
||||
|
||||
public ICollection<selectorBlock> Values
|
||||
{
|
||||
get
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
}
|
||||
|
||||
public void Add(string key, selectorBlock value)
|
||||
{
|
||||
base.Add(new KeyValuePair<string, selectorBlock>(key, value));
|
||||
}
|
||||
|
||||
public bool ContainsKey(string key)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
public bool Remove(string key)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
public bool TryGetValue(string key, out selectorBlock value)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
}
|
||||
|
||||
// Cardigann yaml classes
|
||||
public class IndexerDefinition {
|
||||
public string Site { get; set; }
|
||||
public List<settingsField> Settings { get; set; }
|
||||
public string Name { get; set; }
|
||||
public string Description { get; set; }
|
||||
public string Type { get; set; }
|
||||
public string Language { get; set; }
|
||||
public string Encoding { get; set; }
|
||||
public List<string> Links { get; set; }
|
||||
public List<string> Certificates { get; set; }
|
||||
public capabilitiesBlock Caps { get; set; }
|
||||
public loginBlock Login { get; set; }
|
||||
public ratioBlock Ratio { get; set; }
|
||||
public searchBlock Search { get; set; }
|
||||
public downloadBlock Download { get; set; }
|
||||
// IndexerDefinitionStats not needed/implemented
|
||||
}
|
||||
public class settingsField
|
||||
{
|
||||
public string Name { get; set; }
|
||||
public string Type { get; set; }
|
||||
public string Label { get; set; }
|
||||
public string Default { get; set; }
|
||||
public Dictionary<string, string> Options { get; set; }
|
||||
}
|
||||
|
||||
public class CategorymappingBlock
|
||||
{
|
||||
public string id { get; set; }
|
||||
public string cat { get; set; }
|
||||
public string desc { get; set; }
|
||||
}
|
||||
|
||||
public class capabilitiesBlock
|
||||
{
|
||||
public Dictionary<string, string> Categories { get; set; }
|
||||
public List<CategorymappingBlock> Categorymappings { get; set; }
|
||||
public Dictionary<string, List<string>> Modes { get; set; }
|
||||
}
|
||||
|
||||
public class captchaBlock
|
||||
{
|
||||
public string Type { get; set; }
|
||||
public string Image { get; set; }
|
||||
public string Input { get; set; }
|
||||
}
|
||||
|
||||
public class loginBlock
|
||||
{
|
||||
public string Path { get; set; }
|
||||
public string Submitpath { get; set; }
|
||||
public List<string> Cookies { get; set; }
|
||||
public string Method { get; set; }
|
||||
public string Form { get; set; }
|
||||
public bool Selectors { get; set; } = false;
|
||||
public Dictionary<string, string> Inputs { get; set; }
|
||||
public Dictionary<string, selectorBlock> Selectorinputs { get; set; }
|
||||
public Dictionary<string, selectorBlock> Getselectorinputs { get; set; }
|
||||
public List<errorBlock> Error { get; set; }
|
||||
public pageTestBlock Test { get; set; }
|
||||
public captchaBlock Captcha { get; set; }
|
||||
}
|
||||
|
||||
public class errorBlock
|
||||
{
|
||||
public string Path { get; set; }
|
||||
public string Selector { get; set; }
|
||||
public selectorBlock Message { get; set; }
|
||||
}
|
||||
|
||||
public class selectorBlock
|
||||
{
|
||||
public string Selector { get; set; }
|
||||
public bool Optional { get; set; } = false;
|
||||
public string Text { get; set; }
|
||||
public string Attribute { get; set; }
|
||||
public string Remove { get; set; }
|
||||
public List<filterBlock> Filters { get; set; }
|
||||
public Dictionary<string, string> Case { get; set; }
|
||||
}
|
||||
|
||||
public class filterBlock
|
||||
{
|
||||
public string Name { get; set; }
|
||||
public dynamic Args { get; set; }
|
||||
}
|
||||
|
||||
public class pageTestBlock
|
||||
{
|
||||
public string Path { get; set; }
|
||||
public string Selector { get; set; }
|
||||
}
|
||||
|
||||
public class ratioBlock : selectorBlock
|
||||
{
|
||||
public string Path { get; set; }
|
||||
}
|
||||
|
||||
public class searchBlock
|
||||
{
|
||||
public string Path { get; set; }
|
||||
public List<searchPathBlock> Paths { get; set; }
|
||||
public Dictionary<string, List<string>> Headers { get; set; }
|
||||
public List<filterBlock> Keywordsfilters { get; set; }
|
||||
public Dictionary<string, string> Inputs { get; set; }
|
||||
public List<errorBlock> Error { get; set; }
|
||||
public rowsBlock Rows { get; set; }
|
||||
public KeyValuePairList Fields { get; set; }
|
||||
}
|
||||
|
||||
public class rowsBlock : selectorBlock
|
||||
{
|
||||
public int After { get; set; }
|
||||
//public string Remove { get; set; } // already inherited
|
||||
public selectorBlock Dateheaders { get; set; }
|
||||
}
|
||||
|
||||
public class searchPathBlock : requestBlock
|
||||
{
|
||||
public List<string> Categories { get; set; }
|
||||
public bool Inheritinputs { get; set; } = true;
|
||||
}
|
||||
|
||||
public class requestBlock
|
||||
{
|
||||
public string Path { get; set; }
|
||||
public string Method { get; set; }
|
||||
public Dictionary<string, string> Inputs { get; set; }
|
||||
}
|
||||
|
||||
public class downloadBlock
|
||||
{
|
||||
public string Selector { get; set; }
|
||||
public string Method { get; set; }
|
||||
public requestBlock Before { get; set; }
|
||||
}
|
||||
|
||||
protected readonly string[] OptionalFileds = new string[] { "imdb", "rageid", "tvdbid", "banner" };
|
||||
|
||||
public CardigannIndexer(IIndexerManagerService i, IWebClient wc, Logger l, IProtectionService ps)
|
||||
: base(manager: i,
|
||||
public CardigannIndexer(IIndexerConfigurationService configService, IWebClient wc, Logger l, IProtectionService ps, IndexerDefinition Definition)
|
||||
: base(configService: configService,
|
||||
client: wc,
|
||||
logger: l,
|
||||
p: ps)
|
||||
{
|
||||
}
|
||||
|
||||
public CardigannIndexer(IIndexerManagerService i, IWebClient wc, Logger l, IProtectionService ps, string DefinitionString)
|
||||
: base(manager: i,
|
||||
client: wc,
|
||||
logger: l,
|
||||
p: ps)
|
||||
{
|
||||
Init(DefinitionString);
|
||||
}
|
||||
|
||||
protected void Init(string DefinitionString)
|
||||
{
|
||||
this.DefinitionString = DefinitionString;
|
||||
var deserializer = new DeserializerBuilder()
|
||||
.WithNamingConvention(new CamelCaseNamingConvention())
|
||||
.IgnoreUnmatchedProperties()
|
||||
.Build();
|
||||
Definition = deserializer.Deserialize<IndexerDefinition>(DefinitionString);
|
||||
this.Definition = Definition;
|
||||
|
||||
// Add default data if necessary
|
||||
if (Definition.Settings == null)
|
||||
{
|
||||
{
|
||||
Definition.Settings = new List<settingsField>();
|
||||
Definition.Settings.Add(new settingsField { Name = "username", Label = "Username", Type = "text" });
|
||||
Definition.Settings.Add(new settingsField { Name = "password", Label = "Password", Type = "password" });
|
||||
@@ -310,11 +98,11 @@ namespace Jackett.Indexers
|
||||
switch (Setting.Type)
|
||||
{
|
||||
case "checkbox":
|
||||
item = new BoolItem {Value = false};
|
||||
item = new BoolItem { Value = false };
|
||||
|
||||
if (Setting.Default != null && Setting.Default == "true")
|
||||
{
|
||||
((BoolItem) item).Value = true;
|
||||
((BoolItem)item).Value = true;
|
||||
}
|
||||
break;
|
||||
case "password":
|
||||
@@ -337,13 +125,13 @@ namespace Jackett.Indexers
|
||||
{
|
||||
item = new StringItem { Value = Setting.Default }; ;
|
||||
}
|
||||
|
||||
|
||||
item.Name = Setting.Label;
|
||||
configData.AddDynamic(Setting.Name, item);
|
||||
}
|
||||
|
||||
if (Definition.Caps.Categories != null)
|
||||
{
|
||||
{
|
||||
foreach (var Category in Definition.Caps.Categories)
|
||||
{
|
||||
var cat = TorznabCatType.GetCatByName(Category.Value);
|
||||
@@ -406,10 +194,10 @@ namespace Jackett.Indexers
|
||||
value = ((SelectItem)item).Value;
|
||||
}
|
||||
else
|
||||
{
|
||||
{
|
||||
value = ((StringItem)item).Value;
|
||||
}
|
||||
variables[".Config."+Setting.Name] = value;
|
||||
variables[".Config." + Setting.Name] = value;
|
||||
}
|
||||
return variables;
|
||||
}
|
||||
@@ -547,7 +335,7 @@ namespace Jackett.Indexers
|
||||
}
|
||||
return true; // no error
|
||||
}
|
||||
|
||||
|
||||
protected async Task<bool> DoLogin()
|
||||
{
|
||||
var Login = Definition.Login;
|
||||
@@ -581,7 +369,7 @@ namespace Jackett.Indexers
|
||||
var CaptchaConfigItem = (RecaptchaItem)configData.GetDynamic("Captcha");
|
||||
|
||||
if (CaptchaConfigItem != null)
|
||||
{
|
||||
{
|
||||
if (!string.IsNullOrWhiteSpace(CaptchaConfigItem.Cookie))
|
||||
{
|
||||
// for remote users just set the cookie and return
|
||||
@@ -594,7 +382,7 @@ namespace Jackett.Indexers
|
||||
{
|
||||
var CloudFlareQueryCollection = new NameValueCollection();
|
||||
CloudFlareQueryCollection["id"] = CloudFlareCaptchaChallenge.GetAttribute("data-ray");
|
||||
|
||||
|
||||
CloudFlareQueryCollection["g-recaptcha-response"] = CaptchaConfigItem.Value;
|
||||
var ClearanceUrl = resolvePath("/cdn-cgi/l/chk_captcha?" + CloudFlareQueryCollection.GetQueryString());
|
||||
|
||||
@@ -660,13 +448,13 @@ namespace Jackett.Indexers
|
||||
|
||||
pairs[name] = value;
|
||||
}
|
||||
|
||||
|
||||
foreach (var Input in Definition.Login.Inputs)
|
||||
{
|
||||
var value = applyGoTemplateText(Input.Value);
|
||||
var input = Input.Key;
|
||||
if (Login.Selectors)
|
||||
{
|
||||
{
|
||||
var inputElement = landingResultDocument.QuerySelector(Input.Key);
|
||||
if (inputElement == null)
|
||||
throw new ExceptionWithConfigData(string.Format("Login failed: No input found using selector {0}", Input.Key), configData);
|
||||
@@ -716,7 +504,7 @@ namespace Jackett.Indexers
|
||||
|
||||
// automatically solve simpleCaptchas, if used
|
||||
var simpleCaptchaPresent = landingResultDocument.QuerySelector("script[src*=\"simpleCaptcha\"]");
|
||||
if(simpleCaptchaPresent != null)
|
||||
if (simpleCaptchaPresent != null)
|
||||
{
|
||||
var captchaUrl = resolvePath("simpleCaptcha.php?numImages=1");
|
||||
var simpleCaptchaResult = await RequestStringWithCookies(captchaUrl.ToString(), null, LoginUrl);
|
||||
@@ -736,7 +524,7 @@ namespace Jackett.Indexers
|
||||
{
|
||||
var input = Captcha.Input;
|
||||
if (Login.Selectors)
|
||||
{
|
||||
{
|
||||
var inputElement = landingResultDocument.QuerySelector(Captcha.Input);
|
||||
if (inputElement == null)
|
||||
throw new ExceptionWithConfigData(string.Format("Login failed: No captcha input found using {0}", Captcha.Input), configData);
|
||||
@@ -771,9 +559,11 @@ namespace Jackett.Indexers
|
||||
bodyParts.Add("--" + boundary + "--");
|
||||
|
||||
headers.Add("Content-Type", "multipart/form-data; boundary=" + boundary);
|
||||
var body = string.Join("\r\n", bodyParts);
|
||||
var body = string.Join("\r\n", bodyParts);
|
||||
loginResult = await PostDataWithCookies(submitUrl.ToString(), pairs, configData.CookieHeader.Value, SiteLink, headers, body);
|
||||
} else {
|
||||
}
|
||||
else
|
||||
{
|
||||
loginResult = await RequestLoginAndFollowRedirect(submitUrl.ToString(), pairs, configData.CookieHeader.Value, true, null, LoginUrl, true);
|
||||
}
|
||||
|
||||
@@ -903,7 +693,8 @@ namespace Jackett.Indexers
|
||||
if (Captcha.Type == "image")
|
||||
{
|
||||
var captchaElement = landingResultDocument.QuerySelector(Captcha.Image);
|
||||
if (captchaElement != null) {
|
||||
if (captchaElement != null)
|
||||
{
|
||||
hasCaptcha = true;
|
||||
|
||||
var CaptchaUrl = resolvePath(captchaElement.GetAttribute("src"), LoginUrl);
|
||||
@@ -937,7 +728,7 @@ namespace Jackett.Indexers
|
||||
return configData;
|
||||
}
|
||||
|
||||
public async Task<IndexerConfigurationStatus> ApplyConfiguration(JToken configJson)
|
||||
public override async Task<IndexerConfigurationStatus> ApplyConfiguration(JToken configJson)
|
||||
{
|
||||
LoadValuesFromJson(configJson);
|
||||
|
||||
@@ -954,7 +745,7 @@ namespace Jackett.Indexers
|
||||
if (Filters == null)
|
||||
return Data;
|
||||
|
||||
foreach(filterBlock Filter in Filters)
|
||||
foreach (filterBlock Filter in Filters)
|
||||
{
|
||||
switch (Filter.Name)
|
||||
{
|
||||
@@ -974,7 +765,7 @@ namespace Jackett.Indexers
|
||||
{
|
||||
logger.Debug(ex.Message);
|
||||
}
|
||||
break;
|
||||
break;
|
||||
case "regexp":
|
||||
var pattern = (string)Filter.Args;
|
||||
var Regexp = new Regex(pattern);
|
||||
@@ -987,7 +778,7 @@ namespace Jackett.Indexers
|
||||
regexpreplace_replacement = applyGoTemplateText(regexpreplace_replacement, variables);
|
||||
Regex regexpreplace_regex = new Regex(regexpreplace_pattern);
|
||||
Data = regexpreplace_regex.Replace(Data, regexpreplace_replacement);
|
||||
break;
|
||||
break;
|
||||
case "split":
|
||||
var sep = (string)Filter.Args[0];
|
||||
var pos = (string)Filter.Args[1];
|
||||
@@ -1045,12 +836,12 @@ namespace Jackett.Indexers
|
||||
case "hexdump":
|
||||
// this is mainly for debugging invisible special char related issues
|
||||
var HexData = string.Join("", Data.Select(c => c + "(" + ((int)c).ToString("X2") + ")"));
|
||||
logger.Info(string.Format("CardigannIndexer ({0}): strdump: {1}", ID, HexData));
|
||||
logger.Debug(string.Format("CardigannIndexer ({0}): strdump: {1}", ID, HexData));
|
||||
break;
|
||||
case "strdump":
|
||||
// for debugging
|
||||
var DebugData = Data.Replace("\r", "\\r").Replace("\n", "\\n").Replace("\xA0", "\\xA0");
|
||||
logger.Info(string.Format("CardigannIndexer ({0}): strdump: {1}", ID, DebugData));
|
||||
logger.Debug(string.Format("CardigannIndexer ({0}): strdump: {1}", ID, DebugData));
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
@@ -1097,7 +888,7 @@ namespace Jackett.Indexers
|
||||
|
||||
if (Selector.Remove != null)
|
||||
{
|
||||
foreach(var i in selection.QuerySelectorAll(Selector.Remove))
|
||||
foreach (var i in selection.QuerySelectorAll(Selector.Remove))
|
||||
{
|
||||
i.Remove();
|
||||
}
|
||||
@@ -1105,7 +896,7 @@ namespace Jackett.Indexers
|
||||
|
||||
if (Selector.Case != null)
|
||||
{
|
||||
foreach(var Case in Selector.Case)
|
||||
foreach (var Case in Selector.Case)
|
||||
{
|
||||
if (selection.Matches(Case.Key) || QuerySelector(selection, Case.Key) != null)
|
||||
{
|
||||
@@ -1113,7 +904,7 @@ namespace Jackett.Indexers
|
||||
break;
|
||||
}
|
||||
}
|
||||
if(value == null)
|
||||
if (value == null)
|
||||
throw new Exception(string.Format("None of the case selectors \"{0}\" matched {1}", string.Join(",", Selector.Case), selection.ToHtmlPretty()));
|
||||
}
|
||||
else if (Selector.Attribute != null)
|
||||
@@ -1138,7 +929,7 @@ namespace Jackett.Indexers
|
||||
return new Uri(currentUrl, path);
|
||||
}
|
||||
|
||||
public async Task<IEnumerable<ReleaseInfo>> PerformQuery(TorznabQuery query)
|
||||
protected override async Task<IEnumerable<ReleaseInfo>> PerformQuery(TorznabQuery query)
|
||||
{
|
||||
var releases = new List<ReleaseInfo>();
|
||||
|
||||
@@ -1217,7 +1008,7 @@ namespace Jackett.Indexers
|
||||
InputsList.Add(SearchPath.Inputs);
|
||||
|
||||
foreach (var Inputs in InputsList)
|
||||
{
|
||||
{
|
||||
if (Inputs != null)
|
||||
{
|
||||
foreach (var Input in Inputs)
|
||||
@@ -1467,7 +1258,8 @@ namespace Jackett.Indexers
|
||||
value = release.TVDBId.ToString();
|
||||
break;
|
||||
case "banner":
|
||||
if(!string.IsNullOrWhiteSpace(value)) {
|
||||
if (!string.IsNullOrWhiteSpace(value))
|
||||
{
|
||||
var bannerurl = resolvePath(value, searchUrlUri);
|
||||
release.BannerUrl = bannerurl;
|
||||
}
|
||||
@@ -1512,7 +1304,7 @@ namespace Jackett.Indexers
|
||||
break;
|
||||
case "strdump":
|
||||
// for debugging
|
||||
logger.Info(string.Format("CardigannIndexer ({0}): row strdump: {1}", ID, Row.ToHtmlPretty()));
|
||||
logger.Debug(string.Format("CardigannIndexer ({0}): row strdump: {1}", ID, Row.ToHtmlPretty()));
|
||||
break;
|
||||
default:
|
||||
logger.Error(string.Format("CardigannIndexer ({0}): Unsupported rows filter: {1}", ID, Filter.Name));
|
||||
@@ -1531,7 +1323,7 @@ namespace Jackett.Indexers
|
||||
var PrevRow = Row.PreviousElementSibling;
|
||||
string value = null;
|
||||
if (PrevRow == null) // continue with parent
|
||||
{
|
||||
{
|
||||
var Parent = Row.ParentElement;
|
||||
if (Parent != null)
|
||||
PrevRow = Parent.PreviousElementSibling;
|
||||
@@ -1557,7 +1349,7 @@ namespace Jackett.Indexers
|
||||
PrevRow = Parent.PreviousElementSibling;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
if (value == null && DateHeaders.Optional == false)
|
||||
throw new Exception(string.Format("No date header row found for {0}", release.ToString()));
|
||||
if (value != null)
|
||||
|
||||
@@ -18,13 +18,13 @@ using Jackett.Models.IndexerConfig;
|
||||
|
||||
namespace Jackett.Indexers
|
||||
{
|
||||
public class CinemaZ : AvistazTracker, IIndexer
|
||||
public class CinemaZ : AvistazTracker
|
||||
{
|
||||
public CinemaZ(IIndexerManagerService indexerManager, IWebClient webClient, Logger logger, IProtectionService protectionService)
|
||||
public CinemaZ(IIndexerConfigurationService configService, IWebClient webClient, Logger logger, IProtectionService protectionService)
|
||||
: base(name: "CinemaZ",
|
||||
desc: "Part of the Avistaz network.",
|
||||
link: "https://cinemaz.to/",
|
||||
indexerManager: indexerManager,
|
||||
configService: configService,
|
||||
logger: logger,
|
||||
protectionService: protectionService,
|
||||
webClient: webClient
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
using CsQuery;
|
||||
using CsQuery;
|
||||
using Jackett.Models;
|
||||
using Jackett.Services;
|
||||
using Jackett.Utils.Clients;
|
||||
@@ -18,7 +18,7 @@ using Jackett.Utils;
|
||||
|
||||
namespace Jackett.Indexers
|
||||
{
|
||||
public class DanishBits : BaseIndexer, IIndexer
|
||||
public class DanishBits : BaseWebIndexer
|
||||
{
|
||||
private string LoginUrl { get { return SiteLink + "login.php"; } }
|
||||
private string SearchUrl { get { return SiteLink + "torrents.php"; } }
|
||||
@@ -29,12 +29,12 @@ namespace Jackett.Indexers
|
||||
set { base.configData = value; }
|
||||
}
|
||||
|
||||
public DanishBits(IIndexerManagerService i, Logger l, IWebClient c, IProtectionService ps)
|
||||
public DanishBits(IIndexerConfigurationService configService, IWebClient c, Logger l, IProtectionService ps)
|
||||
: base(name: "DanishBits",
|
||||
description: "A danish closed torrent tracker",
|
||||
link: "https://danishbits.org/",
|
||||
caps: new TorznabCapabilities(),
|
||||
manager: i,
|
||||
configService: configService,
|
||||
client: c,
|
||||
logger: l,
|
||||
p: ps,
|
||||
@@ -125,7 +125,7 @@ namespace Jackett.Indexers
|
||||
AddCategoryMapping(6, TorznabCatType.AudioAudiobook);
|
||||
}
|
||||
|
||||
public async Task<IndexerConfigurationStatus> ApplyConfiguration(JToken configJson)
|
||||
public override async Task<IndexerConfigurationStatus> ApplyConfiguration(JToken configJson)
|
||||
{
|
||||
LoadValuesFromJson(configJson);
|
||||
var pairs = new Dictionary<string, string> {
|
||||
@@ -148,7 +148,7 @@ namespace Jackett.Indexers
|
||||
return IndexerConfigurationStatus.RequiresTesting;
|
||||
}
|
||||
|
||||
public async Task<IEnumerable<ReleaseInfo>> PerformQuery(TorznabQuery query)
|
||||
protected override async Task<IEnumerable<ReleaseInfo>> PerformQuery(TorznabQuery query)
|
||||
{
|
||||
TimeZoneInfo.TransitionTime startTransition = TimeZoneInfo.TransitionTime.CreateFloatingDateRule(new DateTime(1, 1, 1, 2, 0, 0), 3, 5, DayOfWeek.Sunday);
|
||||
TimeZoneInfo.TransitionTime endTransition = TimeZoneInfo.TransitionTime.CreateFloatingDateRule(new DateTime(1, 1, 1, 3, 0, 0), 10, 5, DayOfWeek.Sunday);
|
||||
@@ -160,7 +160,7 @@ namespace Jackett.Indexers
|
||||
var releasesPerPage = 100;
|
||||
var releases = new List<ReleaseInfo>();
|
||||
|
||||
var page = (query.Offset/releasesPerPage) + 1;
|
||||
var page = (query.Offset / releasesPerPage) + 1;
|
||||
|
||||
string episodeSearchUrl;
|
||||
if (string.IsNullOrEmpty(query.GetQueryString()))
|
||||
@@ -212,13 +212,13 @@ namespace Jackett.Indexers
|
||||
var catUrl = catAnchor.GetAttribute("href");
|
||||
var catStr = Regex.Match(catUrl, "filter_(?<catNo>[0-9]+)=on").Groups["catNo"].Value;
|
||||
var catNo = int.Parse(catStr);
|
||||
var moviesCatsDanish = new[] { 2,3,10,28,29,31 };
|
||||
var moviesCatsIntl = new[] { 8,9,11,22,24 };
|
||||
var moviesCatsDanish = new[] { 2, 3, 10, 28, 29, 31 };
|
||||
var moviesCatsIntl = new[] { 8, 9, 11, 22, 24 };
|
||||
var moviesCats = configData.OnlyDanishCategories.Value
|
||||
? moviesCatsDanish
|
||||
: moviesCatsDanish.Concat(moviesCatsIntl);
|
||||
var seriesCatsDanish = new[] { 1,4,30 };
|
||||
var seriesCatsIntl = new[] { 20,21 };
|
||||
var seriesCatsDanish = new[] { 1, 4, 30 };
|
||||
var seriesCatsIntl = new[] { 20, 21 };
|
||||
var seriesCats = configData.OnlyDanishCategories.Value
|
||||
? seriesCatsDanish
|
||||
: seriesCatsDanish.Concat(seriesCatsIntl);
|
||||
@@ -248,7 +248,7 @@ namespace Jackett.Indexers
|
||||
var addedElement = qRow.Find("span.time").FirstElement();
|
||||
var addedStr = addedElement.GetAttribute("title");
|
||||
release.PublishDate = TimeZoneInfo.ConvertTimeToUtc(DateTime.ParseExact(addedStr, "MMM dd yyyy, HH:mm", CultureInfo.InvariantCulture), denmarkTz).ToLocalTime();
|
||||
|
||||
|
||||
var columns = qRow.Children();
|
||||
var seedersElement = columns.Reverse().Skip(1).First();
|
||||
release.Seeders = int.Parse(seedersElement.InnerText);
|
||||
|
||||
@@ -16,7 +16,7 @@ using System.Web;
|
||||
|
||||
namespace Jackett.Indexers
|
||||
{
|
||||
public class Demonoid : BaseIndexer, IIndexer
|
||||
public class Demonoid : BaseWebIndexer
|
||||
{
|
||||
private string LoginUrl { get { return SiteLink + "account_handler.php"; } }
|
||||
private string SearchUrl { get { return SiteLink + "files/?category={0}&subcategory=All&quality=All&seeded=2&to=1&query={1}&external=2"; } }
|
||||
@@ -27,12 +27,12 @@ namespace Jackett.Indexers
|
||||
set { base.configData = value; }
|
||||
}
|
||||
|
||||
public Demonoid(IIndexerManagerService i, Logger l, IWebClient wc, IProtectionService ps)
|
||||
public Demonoid(IIndexerConfigurationService configService, IWebClient wc, Logger l, IProtectionService ps)
|
||||
: base(name: "Demonoid",
|
||||
description: "Demonoid",
|
||||
link: "https://www.demonoid.pw/",
|
||||
caps: TorznabUtil.CreateDefaultTorznabTVCaps(),
|
||||
manager: i,
|
||||
configService: configService,
|
||||
client: wc,
|
||||
logger: l,
|
||||
p: ps,
|
||||
@@ -56,7 +56,7 @@ namespace Jackett.Indexers
|
||||
AddCategoryMapping(3, TorznabCatType.TV, "TV");
|
||||
}
|
||||
|
||||
public async Task<IndexerConfigurationStatus> ApplyConfiguration(JToken configJson)
|
||||
public override async Task<IndexerConfigurationStatus> ApplyConfiguration(JToken configJson)
|
||||
{
|
||||
LoadValuesFromJson(configJson);
|
||||
var pairs = new Dictionary<string, string> {
|
||||
@@ -77,7 +77,7 @@ namespace Jackett.Indexers
|
||||
return IndexerConfigurationStatus.RequiresTesting;
|
||||
}
|
||||
|
||||
public async Task<IEnumerable<ReleaseInfo>> PerformQuery(TorznabQuery query)
|
||||
protected override async Task<IEnumerable<ReleaseInfo>> PerformQuery(TorznabQuery query)
|
||||
{
|
||||
var releases = new List<ReleaseInfo>();
|
||||
var trackerCats = MapTorznabCapsToTrackers(query);
|
||||
|
||||
@@ -17,7 +17,7 @@ using System.IO;
|
||||
|
||||
namespace Jackett.Indexers
|
||||
{
|
||||
public class DigitalHive : BaseIndexer, IIndexer
|
||||
public class DigitalHive : BaseWebIndexer
|
||||
{
|
||||
private string SearchUrl { get { return SiteLink + "browse.php"; } }
|
||||
private string LoginUrl { get { return SiteLink + "login.php?returnto=%2F"; } }
|
||||
@@ -29,12 +29,12 @@ namespace Jackett.Indexers
|
||||
set { base.configData = value; }
|
||||
}
|
||||
|
||||
public DigitalHive(IIndexerManagerService i, Logger l, IWebClient w, IProtectionService ps)
|
||||
public DigitalHive(IIndexerConfigurationService configService, IWebClient w, Logger l, IProtectionService ps)
|
||||
: base(name: "DigitalHive",
|
||||
description: "DigitalHive is one of the oldest general trackers",
|
||||
link: "https://www.digitalhive.org/",
|
||||
caps: new TorznabCapabilities(),
|
||||
manager: i,
|
||||
configService: configService,
|
||||
client: w,
|
||||
logger: l,
|
||||
p: ps,
|
||||
@@ -101,7 +101,7 @@ namespace Jackett.Indexers
|
||||
return result;
|
||||
}
|
||||
|
||||
public async Task<IndexerConfigurationStatus> ApplyConfiguration(JToken configJson)
|
||||
public override async Task<IndexerConfigurationStatus> ApplyConfiguration(JToken configJson)
|
||||
{
|
||||
LoadValuesFromJson(configJson);
|
||||
var pairs = new Dictionary<string, string> {
|
||||
@@ -145,7 +145,7 @@ namespace Jackett.Indexers
|
||||
return IndexerConfigurationStatus.RequiresTesting;
|
||||
}
|
||||
|
||||
public async Task<IEnumerable<ReleaseInfo>> PerformQuery(TorznabQuery query)
|
||||
protected override async Task<IEnumerable<ReleaseInfo>> PerformQuery(TorznabQuery query)
|
||||
{
|
||||
List<ReleaseInfo> releases = new List<ReleaseInfo>();
|
||||
|
||||
|
||||
@@ -18,7 +18,7 @@ using Jackett.Utils.Clients;
|
||||
|
||||
namespace Jackett.Indexers
|
||||
{
|
||||
class EliteTracker : BaseIndexer, IIndexer
|
||||
class EliteTracker : BaseWebIndexer
|
||||
{
|
||||
string LoginUrl { get { return SiteLink + "takelogin.php"; } }
|
||||
string BrowseUrl { get { return SiteLink + "browse.php"; } }
|
||||
@@ -29,11 +29,11 @@ namespace Jackett.Indexers
|
||||
set { base.configData = value; }
|
||||
}
|
||||
|
||||
public EliteTracker(IIndexerManagerService indexerManager, IWebClient webClient, Logger logger, IProtectionService protectionService)
|
||||
public EliteTracker(IIndexerConfigurationService configService, IWebClient webClient, Logger logger, IProtectionService protectionService)
|
||||
: base(name: "Elite-Tracker",
|
||||
description: "French Torrent Tracker",
|
||||
link: "https://elite-tracker.net/",
|
||||
manager: indexerManager,
|
||||
configService: configService,
|
||||
logger: logger,
|
||||
p: protectionService,
|
||||
client: webClient,
|
||||
@@ -122,7 +122,7 @@ namespace Jackett.Indexers
|
||||
AddCategoryMapping(37, TorznabCatType.XXX, "XXX");
|
||||
}
|
||||
|
||||
public async Task<IndexerConfigurationStatus> ApplyConfiguration(JToken configJson)
|
||||
public override async Task<IndexerConfigurationStatus> ApplyConfiguration(JToken configJson)
|
||||
{
|
||||
configData.LoadValuesFromJson(configJson);
|
||||
|
||||
@@ -143,7 +143,7 @@ namespace Jackett.Indexers
|
||||
return IndexerConfigurationStatus.RequiresTesting;
|
||||
}
|
||||
|
||||
public async Task<IEnumerable<ReleaseInfo>> PerformQuery(TorznabQuery query)
|
||||
protected override async Task<IEnumerable<ReleaseInfo>> PerformQuery(TorznabQuery query)
|
||||
{
|
||||
var releases = new List<ReleaseInfo>();
|
||||
var searchString = query.GetQueryString();
|
||||
|
||||
48
src/Jackett/Indexers/Feeds/AnimeTosho.cs
Normal file
48
src/Jackett/Indexers/Feeds/AnimeTosho.cs
Normal file
@@ -0,0 +1,48 @@
|
||||
using System;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Xml.Linq;
|
||||
using Jackett.Models;
|
||||
using Jackett.Models.IndexerConfig;
|
||||
using Jackett.Services;
|
||||
using Jackett.Utils;
|
||||
using Jackett.Utils.Clients;
|
||||
using NLog;
|
||||
|
||||
namespace Jackett.Indexers.Newznab
|
||||
{
|
||||
public class AnimeTosho : BaseNewznabIndexer
|
||||
{
|
||||
public AnimeTosho(IIndexerConfigurationService configService, IWebClient client, Logger logger, IProtectionService p) : base("Anime Tosho", "https://animetosho.org/", "", configService, client, logger, new ConfigurationData(), p)
|
||||
{
|
||||
// TODO
|
||||
// this might be downloaded and refreshed instead of hard-coding it
|
||||
TorznabCaps = new TorznabCapabilities(new TorznabCategory(5070, "Anime"))
|
||||
{
|
||||
SearchAvailable = true,
|
||||
TVSearchAvailable = false,
|
||||
MovieSearchAvailable = false,
|
||||
SupportsImdbSearch = false,
|
||||
SupportsTVRageSearch = false
|
||||
};
|
||||
|
||||
Encoding = Encoding.UTF8;
|
||||
Language = "en-en";
|
||||
Type = "public";
|
||||
}
|
||||
|
||||
protected override ReleaseInfo ResultFromFeedItem(XElement item)
|
||||
{
|
||||
var release = base.ResultFromFeedItem(item);
|
||||
var enclosures = item.Descendants("enclosure").Where(e => e.Attribute("type").Value == "application/x-bittorrent");
|
||||
if (enclosures.Any())
|
||||
{
|
||||
var enclosure = enclosures.First().Attribute("url").Value;
|
||||
release.Link = enclosure.ToUri();
|
||||
}
|
||||
return release;
|
||||
}
|
||||
|
||||
protected override Uri FeedUri => new Uri(SiteLink + "/feed/api");
|
||||
}
|
||||
}
|
||||
52
src/Jackett/Indexers/Feeds/BaseFeedIndexer.cs
Normal file
52
src/Jackett/Indexers/Feeds/BaseFeedIndexer.cs
Normal file
@@ -0,0 +1,52 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Threading.Tasks;
|
||||
using Jackett.Models;
|
||||
using Jackett.Models.IndexerConfig;
|
||||
using Jackett.Services;
|
||||
using Jackett.Utils;
|
||||
using Jackett.Utils.Clients;
|
||||
using Newtonsoft.Json.Linq;
|
||||
using NLog;
|
||||
|
||||
namespace Jackett.Indexers
|
||||
{
|
||||
public abstract class BaseFeedIndexer : BaseWebIndexer
|
||||
{
|
||||
protected abstract Uri FeedUri { get; }
|
||||
|
||||
protected BaseFeedIndexer(string name, string link, string description, IIndexerConfigurationService configService, IWebClient client, Logger logger, ConfigurationData configData, IProtectionService p, TorznabCapabilities caps = null, string downloadBase = null) : base(name, link, description, configService, client, logger, configData, p, caps, downloadBase)
|
||||
{
|
||||
}
|
||||
|
||||
public override Task<IndexerConfigurationStatus> ApplyConfiguration(JToken configJson)
|
||||
{
|
||||
LoadValuesFromJson(configJson);
|
||||
|
||||
IsConfigured = true;
|
||||
SaveConfig();
|
||||
|
||||
return Task.FromResult(IndexerConfigurationStatus.RequiresTesting);
|
||||
}
|
||||
|
||||
protected override async Task<IEnumerable<ReleaseInfo>> PerformQuery(TorznabQuery query)
|
||||
{
|
||||
var requestUri = FeedUri.ToString();
|
||||
if (!query.SearchTerm.IsNullOrEmptyOrWhitespace())
|
||||
requestUri = requestUri + "?q=" + query.SearchTerm;
|
||||
var request = new WebRequest
|
||||
{
|
||||
Url = requestUri,
|
||||
Type = RequestType.GET,
|
||||
Encoding = Encoding
|
||||
};
|
||||
var result = await webclient.GetString(request);
|
||||
|
||||
var results = ParseFeedForResults(result.Content);
|
||||
|
||||
return results;
|
||||
}
|
||||
|
||||
protected abstract IEnumerable<ReleaseInfo> ParseFeedForResults(string feedContent);
|
||||
}
|
||||
}
|
||||
59
src/Jackett/Indexers/Feeds/BaseNewznabIndexer.cs
Normal file
59
src/Jackett/Indexers/Feeds/BaseNewznabIndexer.cs
Normal file
@@ -0,0 +1,59 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Xml.Linq;
|
||||
using Jackett.Models;
|
||||
using Jackett.Models.IndexerConfig;
|
||||
using Jackett.Services;
|
||||
using Jackett.Utils;
|
||||
using Jackett.Utils.Clients;
|
||||
using NLog;
|
||||
|
||||
namespace Jackett.Indexers
|
||||
{
|
||||
public abstract class BaseNewznabIndexer : BaseFeedIndexer
|
||||
{
|
||||
protected BaseNewznabIndexer(string name, string link, string description, IIndexerConfigurationService configService, IWebClient client, Logger logger, ConfigurationData configData, IProtectionService p, TorznabCapabilities caps = null, string downloadBase = null) : base(name, link, description, configService, client, logger, configData, p, caps, downloadBase)
|
||||
{
|
||||
}
|
||||
|
||||
protected override IEnumerable<ReleaseInfo> ParseFeedForResults(string feedContent)
|
||||
{
|
||||
var doc = XDocument.Parse(feedContent);
|
||||
|
||||
var results = doc.Descendants("item").Select(ResultFromFeedItem);
|
||||
|
||||
return results;
|
||||
}
|
||||
|
||||
protected virtual ReleaseInfo ResultFromFeedItem(XElement item)
|
||||
{
|
||||
var attributes = item.Descendants().Where(e => e.Name.LocalName == "attr");
|
||||
var release = new ReleaseInfo
|
||||
{
|
||||
Title = item.FirstValue("title"),
|
||||
Guid = item.FirstValue("guid").ToUri(),
|
||||
Link = item.FirstValue("link").ToUri(),
|
||||
Comments = item.FirstValue("comments").ToUri(),
|
||||
PublishDate = item.FirstValue("pubDate").ToDateTime(),
|
||||
Category = new List<int> { Int32.Parse(attributes.First(e => e.Attribute("name").Value == "category").Attribute("value").Value) },
|
||||
Size = ReadAttribute(attributes, "size").TryParse<Int64>(),
|
||||
Files = ReadAttribute(attributes, "files").TryParse<Int64>(),
|
||||
Description = item.FirstValue("description"),
|
||||
Seeders = ReadAttribute(attributes, "seeders").TryParse<Int32>(),
|
||||
Peers = ReadAttribute(attributes, "peers").TryParse<Int32>(),
|
||||
InfoHash = attributes.First(e => e.Attribute("name").Value == "infohash").Attribute("value").Value,
|
||||
MagnetUri = attributes.First(e => e.Attribute("name").Value == "magneturl").Attribute("value").Value.ToUri(),
|
||||
};
|
||||
return release;
|
||||
}
|
||||
|
||||
private string ReadAttribute(IEnumerable<XElement> attributes, string attributeName)
|
||||
{
|
||||
var attribute = attributes.FirstOrDefault(e => e.Attribute("name").Value == attributeName);
|
||||
if (attribute == null)
|
||||
return "";
|
||||
return attribute.Attribute("value").Value;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -21,7 +21,7 @@ using System.Text.RegularExpressions;
|
||||
|
||||
namespace Jackett.Indexers
|
||||
{
|
||||
public class FileList : BaseIndexer, IIndexer
|
||||
public class FileList : BaseWebIndexer
|
||||
{
|
||||
string LoginUrl { get { return SiteLink + "takelogin.php"; } }
|
||||
string BrowseUrl { get { return SiteLink + "browse.php"; } }
|
||||
@@ -32,12 +32,12 @@ namespace Jackett.Indexers
|
||||
set { base.configData = value; }
|
||||
}
|
||||
|
||||
public FileList(IIndexerManagerService i, IWebClient wc, Logger l, IProtectionService ps)
|
||||
public FileList(IIndexerConfigurationService configService, IWebClient wc, Logger l, IProtectionService ps)
|
||||
: base(name: "FileList",
|
||||
description: "The best Romanian site.",
|
||||
link: "http://filelist.ro/",
|
||||
caps: TorznabUtil.CreateDefaultTorznabTVCaps(),
|
||||
manager: i,
|
||||
configService: configService,
|
||||
client: wc,
|
||||
logger: l,
|
||||
p: ps,
|
||||
@@ -73,7 +73,7 @@ namespace Jackett.Indexers
|
||||
AddCategoryMapping(7, TorznabCatType.XXX); //XXX
|
||||
}
|
||||
|
||||
public async Task<IndexerConfigurationStatus> ApplyConfiguration(JToken configJson)
|
||||
public override async Task<IndexerConfigurationStatus> ApplyConfiguration(JToken configJson)
|
||||
{
|
||||
LoadValuesFromJson(configJson);
|
||||
var pairs = new Dictionary<string, string> {
|
||||
@@ -91,7 +91,7 @@ namespace Jackett.Indexers
|
||||
return IndexerConfigurationStatus.RequiresTesting;
|
||||
}
|
||||
|
||||
public async Task<IEnumerable<ReleaseInfo>> PerformQuery(TorznabQuery query)
|
||||
protected override async Task<IEnumerable<ReleaseInfo>> PerformQuery(TorznabQuery query)
|
||||
{
|
||||
var releases = new List<ReleaseInfo>();
|
||||
var searchUrl = BrowseUrl;
|
||||
@@ -122,6 +122,14 @@ namespace Jackett.Indexers
|
||||
searchUrl += "?" + queryCollection.GetQueryString();
|
||||
|
||||
var response = await RequestStringWithCookiesAndRetry(searchUrl, null, BrowseUrl);
|
||||
|
||||
// Occasionally the cookies become invalid, login again if that happens
|
||||
if (response.IsRedirect)
|
||||
{
|
||||
await ApplyConfiguration(null);
|
||||
response = await RequestStringWithCookiesAndRetry(searchUrl, null, BrowseUrl);
|
||||
}
|
||||
|
||||
var results = response.Content;
|
||||
try
|
||||
{
|
||||
@@ -134,9 +142,12 @@ namespace Jackett.Indexers
|
||||
var qRow = row.Cq();
|
||||
var qTitleLink = qRow.Find(".torrenttable:eq(1) a").First();
|
||||
release.Title = qRow.Find(".torrenttable:eq(1) b").Text();
|
||||
var longtitle = qRow.Find(".torrenttable:eq(1) a[title]").Attr("title");
|
||||
if (!string.IsNullOrEmpty(longtitle) && !longtitle.Contains("<")) // releases with cover image have no full title
|
||||
release.Title = longtitle;
|
||||
|
||||
if (query.ImdbID == null && !query.MatchQueryStringAND(release.Title))
|
||||
continue;
|
||||
continue;
|
||||
|
||||
release.Description = qRow.Find(".torrenttable:eq(1) > span > font.small").First().Text();
|
||||
|
||||
@@ -157,7 +168,7 @@ namespace Jackett.Indexers
|
||||
release.PublishDate = DateTime.ParseExact(dateStr, "H:mm:ssdd/MM/yyyy zzz", CultureInfo.InvariantCulture);
|
||||
|
||||
var qLink = qRow.Find("a[href^=\"download.php?id=\"]").First();
|
||||
release.Link = new Uri(SiteLink + qLink.Attr("href"));
|
||||
release.Link = new Uri(SiteLink + qLink.Attr("href").Replace("&usetoken=1",""));
|
||||
|
||||
var sizeStr = qRow.Find(".torrenttable:eq(6)").Text().Trim();
|
||||
release.Size = ReleaseInfo.GetBytes(sizeStr);
|
||||
|
||||
@@ -14,7 +14,7 @@ using System.Collections.Specialized;
|
||||
|
||||
namespace Jackett.Indexers
|
||||
{
|
||||
public class FunFile : BaseIndexer, IIndexer
|
||||
public class FunFile : BaseWebIndexer
|
||||
{
|
||||
private string SearchUrl { get { return SiteLink + "browse.php"; } }
|
||||
private string LoginUrl { get { return SiteLink + "takelogin.php"; } }
|
||||
@@ -25,12 +25,12 @@ namespace Jackett.Indexers
|
||||
set { base.configData = value; }
|
||||
}
|
||||
|
||||
public FunFile(IIndexerManagerService i, Logger l, IWebClient w, IProtectionService ps)
|
||||
public FunFile(IIndexerConfigurationService configService, IWebClient w, Logger l, IProtectionService ps)
|
||||
: base(name: "FunFile",
|
||||
description: "A general tracker",
|
||||
link: "https://www.funfile.org/",
|
||||
caps: new TorznabCapabilities(),
|
||||
manager: i,
|
||||
configService: configService,
|
||||
client: w,
|
||||
logger: l,
|
||||
p: ps,
|
||||
@@ -52,7 +52,7 @@ namespace Jackett.Indexers
|
||||
AddCategoryMapping(7, TorznabCatType.TV); // TV
|
||||
}
|
||||
|
||||
public async Task<IndexerConfigurationStatus> ApplyConfiguration(JToken configJson)
|
||||
public override async Task<IndexerConfigurationStatus> ApplyConfiguration(JToken configJson)
|
||||
{
|
||||
LoadValuesFromJson(configJson);
|
||||
|
||||
@@ -72,7 +72,7 @@ namespace Jackett.Indexers
|
||||
return IndexerConfigurationStatus.RequiresTesting;
|
||||
}
|
||||
|
||||
public async Task<IEnumerable<ReleaseInfo>> PerformQuery(TorznabQuery query)
|
||||
protected override async Task<IEnumerable<ReleaseInfo>> PerformQuery(TorznabQuery query)
|
||||
{
|
||||
List<ReleaseInfo> releases = new List<ReleaseInfo>();
|
||||
|
||||
|
||||
@@ -20,7 +20,7 @@ using System.Threading;
|
||||
|
||||
namespace Jackett.Indexers
|
||||
{
|
||||
public class Fuzer : BaseIndexer, IIndexer
|
||||
public class Fuzer : BaseWebIndexer
|
||||
{
|
||||
private string SearchUrl { get { return SiteLink + "index.php?name=torrents&"; } }
|
||||
private string LoginUrl { get { return SiteLink + "login.php"; } }
|
||||
@@ -32,11 +32,11 @@ namespace Jackett.Indexers
|
||||
set { base.configData = value; }
|
||||
}
|
||||
|
||||
public Fuzer(IIndexerManagerService i, Logger l, IWebClient w, IProtectionService ps)
|
||||
public Fuzer(IIndexerConfigurationService configService, IWebClient w, Logger l, IProtectionService ps)
|
||||
: base(name: "Fuzer",
|
||||
description: "Fuzer is a private torrent website with israeli torrents.",
|
||||
link: "https://fuzer.me/",
|
||||
manager: i,
|
||||
configService: configService,
|
||||
client: w,
|
||||
logger: l,
|
||||
p: ps,
|
||||
@@ -90,7 +90,7 @@ namespace Jackett.Indexers
|
||||
AddMultiCategoryMapping(TorznabCatType.XXX, 16);
|
||||
}
|
||||
|
||||
public async Task<IndexerConfigurationStatus> ApplyConfiguration(JToken configJson)
|
||||
public override async Task<IndexerConfigurationStatus> ApplyConfiguration(JToken configJson)
|
||||
{
|
||||
LoadValuesFromJson(configJson);
|
||||
var loginPage = await RequestStringWithCookies(LoginUrl, string.Empty);
|
||||
@@ -116,7 +116,7 @@ namespace Jackett.Indexers
|
||||
return IndexerConfigurationStatus.RequiresTesting;
|
||||
}
|
||||
|
||||
public async Task<IEnumerable<ReleaseInfo>> PerformQuery(TorznabQuery query)
|
||||
protected override async Task<IEnumerable<ReleaseInfo>> PerformQuery(TorznabQuery query)
|
||||
{
|
||||
var results = await performRegularQuery(query);
|
||||
if (results.Count() == 0)
|
||||
|
||||
@@ -22,7 +22,7 @@ namespace Jackett.Indexers
|
||||
//
|
||||
// Quick and dirty indexer for GFTracker.
|
||||
//
|
||||
public class GFTracker : BaseIndexer, IIndexer
|
||||
public class GFTracker : BaseWebIndexer
|
||||
{
|
||||
private string StartPageUrl { get { return SiteLink + "login.php?returnto=%2F"; } }
|
||||
private string LoginUrl { get { return SiteLink + "loginsite.php"; } }
|
||||
@@ -34,12 +34,12 @@ namespace Jackett.Indexers
|
||||
set { base.configData = value; }
|
||||
}
|
||||
|
||||
public GFTracker(IIndexerManagerService i, Logger l, IWebClient w, IProtectionService ps)
|
||||
public GFTracker(IIndexerConfigurationService configService, IWebClient w, Logger l, IProtectionService ps)
|
||||
: base(name: "GFTracker",
|
||||
description: "Home of user happiness",
|
||||
link: "https://www.thegft.org/",
|
||||
caps: TorznabUtil.CreateDefaultTorznabTVCaps(),
|
||||
manager: i,
|
||||
configService: configService,
|
||||
client: w,
|
||||
logger: l,
|
||||
p: ps,
|
||||
@@ -105,7 +105,7 @@ namespace Jackett.Indexers
|
||||
}
|
||||
}
|
||||
|
||||
public async Task<IndexerConfigurationStatus> ApplyConfiguration(JToken configJson)
|
||||
public override async Task<IndexerConfigurationStatus> ApplyConfiguration(JToken configJson)
|
||||
{
|
||||
LoadValuesFromJson(configJson);
|
||||
var pairs = new Dictionary<string, string> {
|
||||
@@ -153,7 +153,7 @@ namespace Jackett.Indexers
|
||||
return IndexerConfigurationStatus.RequiresTesting;
|
||||
}
|
||||
|
||||
public async Task<IEnumerable<ReleaseInfo>> PerformQuery(TorznabQuery query)
|
||||
protected override async Task<IEnumerable<ReleaseInfo>> PerformQuery(TorznabQuery query)
|
||||
{
|
||||
var releases = new List<ReleaseInfo>();
|
||||
var searchString = query.GetQueryString();
|
||||
|
||||
@@ -16,7 +16,7 @@ using Jackett.Models.IndexerConfig;
|
||||
|
||||
namespace Jackett.Indexers
|
||||
{
|
||||
public class GhostCity : BaseIndexer, IIndexer
|
||||
public class GhostCity : BaseWebIndexer
|
||||
{
|
||||
string LoginUrl { get { return SiteLink + "takelogin.php"; } }
|
||||
string BrowsePage { get { return SiteLink + "browse.php"; } }
|
||||
@@ -27,12 +27,12 @@ namespace Jackett.Indexers
|
||||
set { base.configData = value; }
|
||||
}
|
||||
|
||||
public GhostCity(IIndexerManagerService i, IWebClient wc, Logger l, IProtectionService ps)
|
||||
public GhostCity(IIndexerConfigurationService configService, IWebClient wc, Logger l, IProtectionService ps)
|
||||
: base(name: "Ghost City",
|
||||
description: "A German general tracker",
|
||||
link: "http://ghostcity.dyndns.info/",
|
||||
caps: TorznabUtil.CreateDefaultTorznabTVCaps(),
|
||||
manager: i,
|
||||
configService: configService,
|
||||
client: wc,
|
||||
logger: l,
|
||||
p: ps,
|
||||
@@ -82,7 +82,7 @@ namespace Jackett.Indexers
|
||||
AddMultiCategoryMapping(TorznabCatType.Other, 3, 93, 24);
|
||||
}
|
||||
|
||||
public async Task<IndexerConfigurationStatus> ApplyConfiguration(JToken configJson)
|
||||
public override async Task<IndexerConfigurationStatus> ApplyConfiguration(JToken configJson)
|
||||
{
|
||||
LoadValuesFromJson(configJson);
|
||||
|
||||
@@ -105,7 +105,7 @@ namespace Jackett.Indexers
|
||||
return IndexerConfigurationStatus.RequiresTesting;
|
||||
}
|
||||
|
||||
public async Task<IEnumerable<ReleaseInfo>> PerformQuery(TorznabQuery query)
|
||||
protected override async Task<IEnumerable<ReleaseInfo>> PerformQuery(TorznabQuery query)
|
||||
{
|
||||
var releases = new List<ReleaseInfo>();
|
||||
var searchString = query.GetQueryString();
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
using System;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.Specialized;
|
||||
using System.Globalization;
|
||||
@@ -18,7 +18,7 @@ using NLog;
|
||||
namespace Jackett.Indexers
|
||||
{
|
||||
// ReSharper disable once InconsistentNaming
|
||||
public class GimmePeers : BaseIndexer, IIndexer
|
||||
public class GimmePeers : BaseWebIndexer
|
||||
{
|
||||
private string BrowseUrl => SiteLink + "browse.php";
|
||||
private string LoginUrl => SiteLink + "takelogin.php";
|
||||
@@ -29,12 +29,12 @@ namespace Jackett.Indexers
|
||||
set { base.configData = value; }
|
||||
}
|
||||
|
||||
public GimmePeers(IIndexerManagerService i, IWebClient wc, Logger l, IProtectionService ps)
|
||||
public GimmePeers(IIndexerConfigurationService configService, IWebClient wc, Logger l, IProtectionService ps)
|
||||
: base(name: "GimmePeers",
|
||||
description: "Formerly ILT",
|
||||
link: "https://www.gimmepeers.com/",
|
||||
caps: new TorznabCapabilities(),
|
||||
manager: i,
|
||||
configService: configService,
|
||||
client: wc,
|
||||
logger: l,
|
||||
p: ps,
|
||||
@@ -71,7 +71,7 @@ namespace Jackett.Indexers
|
||||
AddCategoryMapping(19, TorznabCatType.Movies);
|
||||
}
|
||||
|
||||
public async Task<IndexerConfigurationStatus> ApplyConfiguration(JToken configJson)
|
||||
public override async Task<IndexerConfigurationStatus> ApplyConfiguration(JToken configJson)
|
||||
{
|
||||
LoadValuesFromJson(configJson);
|
||||
var pairs = new Dictionary<string, string> {
|
||||
@@ -94,7 +94,7 @@ namespace Jackett.Indexers
|
||||
return IndexerConfigurationStatus.RequiresTesting;
|
||||
}
|
||||
|
||||
public async Task<IEnumerable<ReleaseInfo>> PerformQuery(TorznabQuery query)
|
||||
protected override async Task<IEnumerable<ReleaseInfo>> PerformQuery(TorznabQuery query)
|
||||
{
|
||||
var releases = new List<ReleaseInfo>();
|
||||
var searchString = query.GetQueryString();
|
||||
|
||||
@@ -16,7 +16,7 @@ using AngleSharp.Parser.Html;
|
||||
|
||||
namespace Jackett.Indexers
|
||||
{
|
||||
public class HD4Free : BaseIndexer, IIndexer
|
||||
public class HD4Free : BaseWebIndexer
|
||||
{
|
||||
private string SearchUrl { get { return SiteLink + "ajax/initial_recall.php"; } }
|
||||
private string LoginUrl { get { return SiteLink + "login.php"; } }
|
||||
@@ -28,12 +28,12 @@ namespace Jackett.Indexers
|
||||
set { base.configData = value; }
|
||||
}
|
||||
|
||||
public HD4Free(IIndexerManagerService i, Logger l, IWebClient w, IProtectionService ps)
|
||||
public HD4Free(IIndexerConfigurationService configService, IWebClient w, Logger l, IProtectionService ps)
|
||||
: base(name: "HD4Free",
|
||||
description: "A HD trackers",
|
||||
link: "https://hd4free.xyz/",
|
||||
caps: new TorznabCapabilities(),
|
||||
manager: i,
|
||||
configService: configService,
|
||||
client: w,
|
||||
logger: l,
|
||||
p: ps,
|
||||
@@ -105,7 +105,7 @@ namespace Jackett.Indexers
|
||||
return result;
|
||||
}
|
||||
|
||||
public async Task<IndexerConfigurationStatus> ApplyConfiguration(JToken configJson)
|
||||
public override async Task<IndexerConfigurationStatus> ApplyConfiguration(JToken configJson)
|
||||
{
|
||||
LoadValuesFromJson(configJson);
|
||||
var pairs = new Dictionary<string, string> {
|
||||
@@ -154,7 +154,7 @@ namespace Jackett.Indexers
|
||||
return IndexerConfigurationStatus.RequiresTesting;
|
||||
}
|
||||
|
||||
public async Task<IEnumerable<ReleaseInfo>> PerformQuery(TorznabQuery query)
|
||||
protected override async Task<IEnumerable<ReleaseInfo>> PerformQuery(TorznabQuery query)
|
||||
{
|
||||
List<ReleaseInfo> releases = new List<ReleaseInfo>();
|
||||
|
||||
|
||||
@@ -6,13 +6,13 @@ using Jackett.Indexers.Abstract;
|
||||
|
||||
namespace Jackett.Indexers
|
||||
{
|
||||
public class HDForever : GazelleTracker, IIndexer
|
||||
public class HDForever : GazelleTracker
|
||||
{
|
||||
public HDForever(IIndexerManagerService indexerManager, IWebClient webClient, Logger logger, IProtectionService protectionService)
|
||||
public HDForever(IIndexerConfigurationService configService, IWebClient webClient, Logger logger, IProtectionService protectionService)
|
||||
: base(name: "HD-Forever",
|
||||
desc: null,
|
||||
link: "https://hdf.world/",
|
||||
indexerManager: indexerManager,
|
||||
configService: configService,
|
||||
logger: logger,
|
||||
protectionService: protectionService,
|
||||
webClient: webClient
|
||||
|
||||
@@ -9,13 +9,13 @@ using System.Web;
|
||||
|
||||
namespace Jackett.Indexers
|
||||
{
|
||||
public class HDOnly : GazelleTracker, IIndexer
|
||||
public class HDOnly : GazelleTracker
|
||||
{
|
||||
public HDOnly(IIndexerManagerService indexerManager, IWebClient webClient, Logger logger, IProtectionService protectionService)
|
||||
public HDOnly(IIndexerConfigurationService configService, IWebClient webClient, Logger logger, IProtectionService protectionService)
|
||||
: base(name: "HD-Only",
|
||||
desc: null,
|
||||
link: "https://hd-only.org/",
|
||||
indexerManager: indexerManager,
|
||||
configService: configService,
|
||||
logger: logger,
|
||||
protectionService: protectionService,
|
||||
webClient: webClient
|
||||
|
||||
@@ -18,7 +18,7 @@ using System.Collections.Specialized;
|
||||
|
||||
namespace Jackett.Indexers
|
||||
{
|
||||
public class HDSpace : BaseIndexer, IIndexer
|
||||
public class HDSpace : BaseWebIndexer
|
||||
{
|
||||
private string LoginUrl { get { return SiteLink + "index.php?page=login"; } }
|
||||
private string SearchUrl { get { return SiteLink + "index.php?page=torrents&"; } }
|
||||
@@ -29,12 +29,12 @@ namespace Jackett.Indexers
|
||||
set { base.configData = value; }
|
||||
}
|
||||
|
||||
public HDSpace(IIndexerManagerService i, IWebClient wc, Logger l, IProtectionService ps)
|
||||
public HDSpace(IIndexerConfigurationService configService, IWebClient wc, Logger l, IProtectionService ps)
|
||||
: base(name: "HD-Space",
|
||||
description: "Sharing The Universe",
|
||||
link: "https://hd-space.org/",
|
||||
caps: TorznabUtil.CreateDefaultTorznabTVCaps(),
|
||||
manager: i,
|
||||
configService: configService,
|
||||
client: wc,
|
||||
logger: l,
|
||||
p: ps,
|
||||
@@ -75,7 +75,7 @@ namespace Jackett.Indexers
|
||||
AddCategoryMapping("38", TorznabCatType.Other);
|
||||
}
|
||||
|
||||
public async Task<IndexerConfigurationStatus> ApplyConfiguration(JToken configJson)
|
||||
public override async Task<IndexerConfigurationStatus> ApplyConfiguration(JToken configJson)
|
||||
{
|
||||
LoadValuesFromJson(configJson);
|
||||
|
||||
@@ -100,7 +100,7 @@ namespace Jackett.Indexers
|
||||
return IndexerConfigurationStatus.RequiresTesting;
|
||||
}
|
||||
|
||||
public async Task<IEnumerable<ReleaseInfo>> PerformQuery(TorznabQuery query)
|
||||
protected override async Task<IEnumerable<ReleaseInfo>> PerformQuery(TorznabQuery query)
|
||||
{
|
||||
var releases = new List<ReleaseInfo>();
|
||||
|
||||
|
||||
@@ -19,7 +19,7 @@ using System.Collections.Specialized;
|
||||
|
||||
namespace Jackett.Indexers
|
||||
{
|
||||
public class HDTorrents : BaseIndexer, IIndexer
|
||||
public class HDTorrents : BaseWebIndexer
|
||||
{
|
||||
private string SearchUrl { get { return SiteLink + "torrents.php?"; } }
|
||||
private string LoginUrl { get { return SiteLink + "login.php"; } }
|
||||
@@ -32,11 +32,11 @@ namespace Jackett.Indexers
|
||||
set { base.configData = value; }
|
||||
}
|
||||
|
||||
public HDTorrents(IIndexerManagerService i, Logger l, IWebClient w, IProtectionService ps)
|
||||
public HDTorrents(IIndexerConfigurationService configService, IWebClient w, Logger l, IProtectionService ps)
|
||||
: base(name: "HD-Torrents",
|
||||
description: "HD-Torrents is a private torrent website with HD torrents and strict rules on their content.",
|
||||
link: "https://hdts.ru/",// Of the accessible domains the .ru seems the most reliable. https://hdts.ru | https://hd-torrents.org | https://hd-torrents.net | https://hd-torrents.me
|
||||
manager: i,
|
||||
configService: configService,
|
||||
client: w,
|
||||
logger: l,
|
||||
p: ps,
|
||||
@@ -79,7 +79,7 @@ namespace Jackett.Indexers
|
||||
AddCategoryMapping("67", TorznabCatType.Movies3D, "3D");
|
||||
}
|
||||
|
||||
public async Task<IndexerConfigurationStatus> ApplyConfiguration(JToken configJson)
|
||||
public override async Task<IndexerConfigurationStatus> ApplyConfiguration(JToken configJson)
|
||||
{
|
||||
LoadValuesFromJson(configJson);
|
||||
|
||||
@@ -100,7 +100,7 @@ namespace Jackett.Indexers
|
||||
return IndexerConfigurationStatus.RequiresTesting;
|
||||
}
|
||||
|
||||
public async Task<IEnumerable<ReleaseInfo>> PerformQuery(TorznabQuery query)
|
||||
protected override async Task<IEnumerable<ReleaseInfo>> PerformQuery(TorznabQuery query)
|
||||
{
|
||||
var releases = new List<ReleaseInfo>();
|
||||
var searchurls = new List<string>();
|
||||
|
||||
@@ -15,7 +15,7 @@ using System.Globalization;
|
||||
|
||||
namespace Jackett.Indexers
|
||||
{
|
||||
public class Hardbay : BaseIndexer, IIndexer
|
||||
public class Hardbay : BaseWebIndexer
|
||||
{
|
||||
private string SearchUrl { get { return SiteLink + "api/v1/torrents"; } }
|
||||
private string LoginUrl { get { return SiteLink + "api/v1/auth"; } }
|
||||
@@ -26,12 +26,12 @@ namespace Jackett.Indexers
|
||||
set { base.configData = value; }
|
||||
}
|
||||
|
||||
public Hardbay(IIndexerManagerService i, Logger l, IWebClient w, IProtectionService ps)
|
||||
public Hardbay(IIndexerConfigurationService configService, IWebClient w, Logger l, IProtectionService ps)
|
||||
: base(name: "Hardbay",
|
||||
description: null,
|
||||
link: "https://hardbay.club/",
|
||||
caps: new TorznabCapabilities(),
|
||||
manager: i,
|
||||
configService: configService,
|
||||
client: w,
|
||||
logger: l,
|
||||
p: ps,
|
||||
@@ -45,7 +45,7 @@ namespace Jackett.Indexers
|
||||
AddCategoryMapping(2, TorznabCatType.AudioLossless);
|
||||
}
|
||||
|
||||
public async Task<IndexerConfigurationStatus> ApplyConfiguration(JToken configJson)
|
||||
public override async Task<IndexerConfigurationStatus> ApplyConfiguration(JToken configJson)
|
||||
{
|
||||
LoadValuesFromJson(configJson);
|
||||
var queryCollection = new NameValueCollection();
|
||||
@@ -63,7 +63,7 @@ namespace Jackett.Indexers
|
||||
return IndexerConfigurationStatus.RequiresTesting;
|
||||
}
|
||||
|
||||
public async Task<IEnumerable<ReleaseInfo>> PerformQuery(TorznabQuery query)
|
||||
protected override async Task<IEnumerable<ReleaseInfo>> PerformQuery(TorznabQuery query)
|
||||
{
|
||||
List<ReleaseInfo> releases = new List<ReleaseInfo>();
|
||||
var queryCollection = new NameValueCollection();
|
||||
|
||||
@@ -16,7 +16,7 @@ using System.Text;
|
||||
|
||||
namespace Jackett.Indexers
|
||||
{
|
||||
public class Hebits : BaseIndexer, IIndexer
|
||||
public class Hebits : BaseWebIndexer
|
||||
{
|
||||
private string LoginUrl { get { return SiteLink + "login.php"; } }
|
||||
private string LoginPostUrl { get { return SiteLink + "takeloginAjax.php"; } }
|
||||
@@ -28,12 +28,12 @@ namespace Jackett.Indexers
|
||||
set { base.configData = value; }
|
||||
}
|
||||
|
||||
public Hebits(IIndexerManagerService i, Logger l, IWebClient wc, IProtectionService ps)
|
||||
public Hebits(IIndexerConfigurationService configService, IWebClient wc, Logger l, IProtectionService ps)
|
||||
: base(name: "Hebits",
|
||||
description: "The Israeli Tracker",
|
||||
link: "https://hebits.net/",
|
||||
caps: TorznabUtil.CreateDefaultTorznabTVCaps(),
|
||||
manager: i,
|
||||
configService: configService,
|
||||
client: wc,
|
||||
logger: l,
|
||||
p: ps,
|
||||
@@ -56,7 +56,7 @@ namespace Jackett.Indexers
|
||||
AddCategoryMapping(37, TorznabCatType.TVHD); // Israel HDTV
|
||||
}
|
||||
|
||||
public async Task<IndexerConfigurationStatus> ApplyConfiguration(JToken configJson)
|
||||
public override async Task<IndexerConfigurationStatus> ApplyConfiguration(JToken configJson)
|
||||
{
|
||||
LoadValuesFromJson(configJson);
|
||||
var pairs = new Dictionary<string, string> {
|
||||
@@ -77,7 +77,7 @@ namespace Jackett.Indexers
|
||||
return IndexerConfigurationStatus.RequiresTesting;
|
||||
}
|
||||
|
||||
public async Task<IEnumerable<ReleaseInfo>> PerformQuery(TorznabQuery query)
|
||||
protected override async Task<IEnumerable<ReleaseInfo>> PerformQuery(TorznabQuery query)
|
||||
{
|
||||
var releases = new List<ReleaseInfo>();
|
||||
var searchString = query.GetQueryString();
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
using CsQuery;
|
||||
using CsQuery;
|
||||
using Jackett.Indexers;
|
||||
using Jackett.Models;
|
||||
using Jackett.Services;
|
||||
@@ -23,28 +23,28 @@ using System.Text.RegularExpressions;
|
||||
|
||||
namespace Jackett.Indexers
|
||||
{
|
||||
public class Hounddawgs : BaseIndexer, IIndexer
|
||||
{
|
||||
private string LoginUrl { get { return SiteLink + "login.php"; } }
|
||||
private string SearchUrl { get { return SiteLink + "torrents.php"; } }
|
||||
public class Hounddawgs : BaseWebIndexer
|
||||
{
|
||||
private string LoginUrl { get { return SiteLink + "login.php"; } }
|
||||
private string SearchUrl { get { return SiteLink + "torrents.php"; } }
|
||||
|
||||
new NxtGnConfigurationData configData
|
||||
{
|
||||
get { return (NxtGnConfigurationData)base.configData; }
|
||||
set { base.configData = value; }
|
||||
}
|
||||
new NxtGnConfigurationData configData
|
||||
{
|
||||
get { return (NxtGnConfigurationData)base.configData; }
|
||||
set { base.configData = value; }
|
||||
}
|
||||
|
||||
public Hounddawgs(IIndexerManagerService i, Logger l, IWebClient c, IProtectionService ps)
|
||||
: base(name: "Hounddawgs",
|
||||
description: "A danish closed torrent tracker",
|
||||
link: "https://hounddawgs.org/",
|
||||
caps: TorznabUtil.CreateDefaultTorznabTVCaps(),
|
||||
manager: i,
|
||||
client: c,
|
||||
logger: l,
|
||||
p: ps,
|
||||
configData: new NxtGnConfigurationData())
|
||||
{
|
||||
public Hounddawgs(IIndexerConfigurationService configService, IWebClient c, Logger l, IProtectionService ps)
|
||||
: base(name: "Hounddawgs",
|
||||
description: "A danish closed torrent tracker",
|
||||
link: "https://hounddawgs.org/",
|
||||
caps: TorznabUtil.CreateDefaultTorznabTVCaps(),
|
||||
configService: configService,
|
||||
client: c,
|
||||
logger: l,
|
||||
p: ps,
|
||||
configData: new NxtGnConfigurationData())
|
||||
{
|
||||
Encoding = Encoding.GetEncoding("UTF-8");
|
||||
Language = "da-dk";
|
||||
Type = "private";
|
||||
@@ -84,32 +84,32 @@ namespace Jackett.Indexers
|
||||
AddCategoryMapping(67, TorznabCatType.XXX, "XXX");
|
||||
}
|
||||
|
||||
public async Task<IndexerConfigurationStatus> ApplyConfiguration(JToken configJson)
|
||||
{
|
||||
LoadValuesFromJson(configJson);
|
||||
var pairs = new Dictionary<string, string> {
|
||||
{ "username", configData.Username.Value },
|
||||
{ "password", configData.Password.Value },
|
||||
{ "keeplogged", "1" },
|
||||
{ "login", "Login" }
|
||||
public override async Task<IndexerConfigurationStatus> ApplyConfiguration(JToken configJson)
|
||||
{
|
||||
LoadValuesFromJson(configJson);
|
||||
var pairs = new Dictionary<string, string> {
|
||||
{ "username", configData.Username.Value },
|
||||
{ "password", configData.Password.Value },
|
||||
{ "keeplogged", "1" },
|
||||
{ "login", "Login" }
|
||||
|
||||
};
|
||||
// Get inital cookies
|
||||
};
|
||||
// Get inital cookies
|
||||
var response = await RequestLoginAndFollowRedirect(LoginUrl, pairs, null, true, null, "https://hounddawgs.org/");
|
||||
|
||||
await ConfigureIfOK(response.Cookies, response.Content != null && response.Content.Contains("Velkommen til"), () =>
|
||||
{
|
||||
CQ dom = response.Content;
|
||||
var messageEl = dom["inputs"];
|
||||
var errorMessage = messageEl.Text().Trim();
|
||||
throw new ExceptionWithConfigData(errorMessage, configData);
|
||||
});
|
||||
return IndexerConfigurationStatus.RequiresTesting;
|
||||
}
|
||||
await ConfigureIfOK(response.Cookies, response.Content != null && response.Content.Contains("Velkommen til"), () =>
|
||||
{
|
||||
CQ dom = response.Content;
|
||||
var messageEl = dom["inputs"];
|
||||
var errorMessage = messageEl.Text().Trim();
|
||||
throw new ExceptionWithConfigData(errorMessage, configData);
|
||||
});
|
||||
return IndexerConfigurationStatus.RequiresTesting;
|
||||
}
|
||||
|
||||
public async Task<IEnumerable<ReleaseInfo>> PerformQuery(TorznabQuery query)
|
||||
{
|
||||
var releases = new List<ReleaseInfo>();
|
||||
protected override async Task<IEnumerable<ReleaseInfo>> PerformQuery(TorznabQuery query)
|
||||
{
|
||||
var releases = new List<ReleaseInfo>();
|
||||
var searchString = query.GetQueryString();
|
||||
var searchUrl = SearchUrl;
|
||||
var queryCollection = new NameValueCollection();
|
||||
@@ -129,34 +129,34 @@ namespace Jackett.Indexers
|
||||
|
||||
searchUrl += "?" + queryCollection.GetQueryString();
|
||||
var results = await RequestStringWithCookiesAndRetry(searchUrl);
|
||||
if (results.Content.Contains("Din søgning gav intet resultat."))
|
||||
{
|
||||
return releases;
|
||||
}
|
||||
try
|
||||
{
|
||||
CQ dom = results.Content;
|
||||
if (results.Content.Contains("Din søgning gav intet resultat."))
|
||||
{
|
||||
return releases;
|
||||
}
|
||||
try
|
||||
{
|
||||
CQ dom = results.Content;
|
||||
|
||||
var rows = dom["#torrent_table > tbody > tr"].ToArray();
|
||||
var rows = dom["#torrent_table > tbody > tr"].ToArray();
|
||||
|
||||
foreach (var row in rows.Skip(1))
|
||||
{
|
||||
foreach (var row in rows.Skip(1))
|
||||
{
|
||||
var qRow = row.Cq();
|
||||
var release = new ReleaseInfo();
|
||||
release.MinimumRatio = 1;
|
||||
release.MinimumSeedTime = 172800;
|
||||
release.MinimumRatio = 1;
|
||||
release.MinimumSeedTime = 172800;
|
||||
|
||||
var qCat = row.ChildElements.ElementAt(0).ChildElements.ElementAt(0).Cq();
|
||||
var catUrl = qCat.Attr("href");
|
||||
var cat = catUrl.Substring(catUrl.LastIndexOf('[') + 1).Trim(']');
|
||||
var qCat = row.ChildElements.ElementAt(0).ChildElements.ElementAt(0).Cq();
|
||||
var catUrl = qCat.Attr("href");
|
||||
var cat = catUrl.Substring(catUrl.LastIndexOf('[') + 1).Trim(']');
|
||||
release.Category = MapTrackerCatToNewznab(cat);
|
||||
|
||||
var qAdded = row.ChildElements.ElementAt(4).ChildElements.ElementAt(0).Cq();
|
||||
var addedStr = qAdded.Attr("title");
|
||||
release.PublishDate = DateTime.ParseExact(addedStr, "MMM dd yyyy, HH:mm", CultureInfo.InvariantCulture);
|
||||
var addedStr = qAdded.Attr("title");
|
||||
release.PublishDate = DateTime.ParseExact(addedStr, "MMM dd yyyy, HH:mm", CultureInfo.InvariantCulture);
|
||||
|
||||
var overlayScript = qRow.Find("script:contains(\"var overlay\")").Text();
|
||||
var overlayHtmlEscaped = overlayScript.Substring(overlayScript.IndexOf('=')+1).Trim().Trim('"');
|
||||
var overlayHtmlEscaped = overlayScript.Substring(overlayScript.IndexOf('=') + 1).Trim().Trim('"');
|
||||
var overlayHtml = Regex.Unescape(overlayHtmlEscaped);
|
||||
CQ qOverlay = overlayHtml;
|
||||
var title = qOverlay.Find("td.overlay > strong");
|
||||
@@ -177,46 +177,46 @@ namespace Jackett.Indexers
|
||||
|
||||
var qLink = row.Cq().Find("a[href^=\"torrents.php?id=\"][onmouseover]");
|
||||
release.Comments = new Uri(SiteLink + qLink.Attr("href"));
|
||||
release.Guid = release.Comments;
|
||||
release.Guid = release.Comments;
|
||||
|
||||
var qDownload = row.ChildElements.ElementAt(1).ChildElements.ElementAt(1).ChildElements.ElementAt(0).Cq();
|
||||
release.Link = new Uri(SiteLink + qDownload.Attr("href"));
|
||||
var qDownload = row.ChildElements.ElementAt(1).ChildElements.ElementAt(1).ChildElements.ElementAt(0).Cq();
|
||||
release.Link = new Uri(SiteLink + qDownload.Attr("href"));
|
||||
|
||||
var sizeStr = row.ChildElements.ElementAt(5).Cq().Text();
|
||||
release.Size = ReleaseInfo.GetBytes(sizeStr);
|
||||
var sizeStr = row.ChildElements.ElementAt(5).Cq().Text();
|
||||
release.Size = ReleaseInfo.GetBytes(sizeStr);
|
||||
|
||||
release.Seeders = ParseUtil.CoerceInt(row.ChildElements.ElementAt(6).Cq().Text());
|
||||
release.Peers = ParseUtil.CoerceInt(row.ChildElements.ElementAt(7).Cq().Text()) + release.Seeders;
|
||||
release.Seeders = ParseUtil.CoerceInt(row.ChildElements.ElementAt(6).Cq().Text());
|
||||
release.Peers = ParseUtil.CoerceInt(row.ChildElements.ElementAt(7).Cq().Text()) + release.Seeders;
|
||||
|
||||
var files = row.Cq().Find("td:nth-child(4)").Text();
|
||||
release.Files = ParseUtil.CoerceInt(files);
|
||||
|
||||
if (row.Cq().Find("img[src=\"/static//common/browse/freeleech.png\"]").Any())
|
||||
release.DownloadVolumeFactor = 0;
|
||||
release.DownloadVolumeFactor = 0;
|
||||
else
|
||||
release.DownloadVolumeFactor = 1;
|
||||
|
||||
release.UploadVolumeFactor = 1;
|
||||
|
||||
releases.Add(release);
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
OnParseError(results.Content, ex);
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
OnParseError(results.Content, ex);
|
||||
}
|
||||
|
||||
return releases;
|
||||
}
|
||||
public class NxtGnConfigurationData : ConfigurationData
|
||||
{
|
||||
public NxtGnConfigurationData()
|
||||
{
|
||||
Username = new StringItem { Name = "Username" };
|
||||
Password = new StringItem { Name = "Password" };
|
||||
}
|
||||
public StringItem Username { get; private set; }
|
||||
public StringItem Password { get; private set; }
|
||||
}
|
||||
}
|
||||
return releases;
|
||||
}
|
||||
public class NxtGnConfigurationData : ConfigurationData
|
||||
{
|
||||
public NxtGnConfigurationData()
|
||||
{
|
||||
Username = new StringItem { Name = "Username" };
|
||||
Password = new StringItem { Name = "Password" };
|
||||
}
|
||||
public StringItem Username { get; private set; }
|
||||
public StringItem Password { get; private set; }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -17,7 +17,7 @@ using System.Text.RegularExpressions;
|
||||
|
||||
namespace Jackett.Indexers
|
||||
{
|
||||
public class HouseOfTorrents : BaseIndexer, IIndexer
|
||||
public class HouseOfTorrents : BaseWebIndexer
|
||||
{
|
||||
private string SearchUrl { get { return SiteLink + "browse.php"; } }
|
||||
private string LoginUrl { get { return SiteLink + "takelogin.php"; } }
|
||||
@@ -29,12 +29,12 @@ namespace Jackett.Indexers
|
||||
set { base.configData = value; }
|
||||
}
|
||||
|
||||
public HouseOfTorrents(IIndexerManagerService i, Logger l, IWebClient w, IProtectionService ps)
|
||||
public HouseOfTorrents(IIndexerConfigurationService configService, IWebClient w, Logger l, IProtectionService ps)
|
||||
: base(name: "House-of-Torrents",
|
||||
description: "A general tracker",
|
||||
link: "https://houseoftorrents.club/",
|
||||
caps: new TorznabCapabilities(),
|
||||
manager: i,
|
||||
configService: configService,
|
||||
client: w,
|
||||
logger: l,
|
||||
p: ps,
|
||||
@@ -97,7 +97,7 @@ namespace Jackett.Indexers
|
||||
AddCategoryMapping(81, TorznabCatType.XXXPacks); // XXX/Pack
|
||||
}
|
||||
|
||||
public async Task<IndexerConfigurationStatus> ApplyConfiguration(JToken configJson)
|
||||
public override async Task<IndexerConfigurationStatus> ApplyConfiguration(JToken configJson)
|
||||
{
|
||||
LoadValuesFromJson(configJson);
|
||||
|
||||
@@ -127,7 +127,7 @@ namespace Jackett.Indexers
|
||||
return IndexerConfigurationStatus.RequiresTesting;
|
||||
}
|
||||
|
||||
public async Task<IEnumerable<ReleaseInfo>> PerformQuery(TorznabQuery query)
|
||||
protected override async Task<IEnumerable<ReleaseInfo>> PerformQuery(TorznabQuery query)
|
||||
{
|
||||
List<ReleaseInfo> releases = new List<ReleaseInfo>();
|
||||
|
||||
|
||||
@@ -37,15 +37,15 @@ namespace Jackett.Indexers
|
||||
void LoadFromSavedConfiguration(JToken jsonConfig);
|
||||
void SaveConfig();
|
||||
|
||||
Task<IEnumerable<ReleaseInfo>> PerformQuery(TorznabQuery query);
|
||||
void Unconfigure();
|
||||
|
||||
IEnumerable<ReleaseInfo> FilterResults(TorznabQuery query, IEnumerable<ReleaseInfo> input);
|
||||
|
||||
Task<byte[]> Download(Uri link);
|
||||
|
||||
IEnumerable<ReleaseInfo> CleanLinks(IEnumerable<ReleaseInfo> releases);
|
||||
Uri UncleanLink(Uri link);
|
||||
Task<IEnumerable<ReleaseInfo>> ResultsForQuery(TorznabQuery query);
|
||||
|
||||
bool CanHandleQuery(TorznabQuery query);
|
||||
}
|
||||
|
||||
public interface IWebIndexer : IIndexer
|
||||
{
|
||||
Task<byte[]> Download(Uri link);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -19,7 +19,7 @@ using Jackett.Models.IndexerConfig;
|
||||
|
||||
namespace Jackett.Indexers
|
||||
{
|
||||
public class IPTorrents : BaseIndexer, IIndexer
|
||||
public class IPTorrents : BaseWebIndexer
|
||||
{
|
||||
string LoginUrl { get { return SiteLink + "login.php"; } }
|
||||
string TakeLoginUrl { get { return SiteLink + "take_login.php"; } }
|
||||
@@ -32,12 +32,12 @@ namespace Jackett.Indexers
|
||||
set { base.configData = value; }
|
||||
}
|
||||
|
||||
public IPTorrents(IIndexerManagerService i, IWebClient wc, Logger l, IProtectionService ps)
|
||||
public IPTorrents(IIndexerConfigurationService configService, IWebClient wc, Logger l, IProtectionService ps)
|
||||
: base(name: "IPTorrents",
|
||||
description: "Always a step ahead.",
|
||||
link: "https://iptorrents.com/",
|
||||
caps: new TorznabCapabilities(),
|
||||
manager: i,
|
||||
configService: configService,
|
||||
client: wc,
|
||||
logger: l,
|
||||
p: ps,
|
||||
@@ -149,7 +149,7 @@ namespace Jackett.Indexers
|
||||
|
||||
|
||||
|
||||
public async Task<IndexerConfigurationStatus> ApplyConfiguration(JToken configJson)
|
||||
public override async Task<IndexerConfigurationStatus> ApplyConfiguration(JToken configJson)
|
||||
{
|
||||
LoadValuesFromJson(configJson);
|
||||
var pairs = new Dictionary<string, string> {
|
||||
@@ -203,7 +203,7 @@ namespace Jackett.Indexers
|
||||
return IndexerConfigurationStatus.RequiresTesting;
|
||||
}
|
||||
|
||||
public async Task<IEnumerable<ReleaseInfo>> PerformQuery(TorznabQuery query)
|
||||
protected override async Task<IEnumerable<ReleaseInfo>> PerformQuery(TorznabQuery query)
|
||||
{
|
||||
var releases = new List<ReleaseInfo>();
|
||||
var searchString = query.GetQueryString();
|
||||
|
||||
@@ -18,7 +18,7 @@ using Jackett.Models.IndexerConfig;
|
||||
|
||||
namespace Jackett.Indexers
|
||||
{
|
||||
public class ImmortalSeed : BaseIndexer, IIndexer
|
||||
public class ImmortalSeed : BaseWebIndexer
|
||||
{
|
||||
private string BrowsePage { get { return SiteLink + "browse.php"; } }
|
||||
private string LoginUrl { get { return SiteLink + "takelogin.php"; } }
|
||||
@@ -30,12 +30,12 @@ namespace Jackett.Indexers
|
||||
set { base.configData = value; }
|
||||
}
|
||||
|
||||
public ImmortalSeed(IIndexerManagerService i, IWebClient wc, Logger l, IProtectionService ps)
|
||||
public ImmortalSeed(IIndexerConfigurationService configService, IWebClient wc, Logger l, IProtectionService ps)
|
||||
: base(name: "ImmortalSeed",
|
||||
description: "ImmortalSeed",
|
||||
link: "http://immortalseed.me/",
|
||||
caps: TorznabUtil.CreateDefaultTorznabTVCaps(),
|
||||
manager: i,
|
||||
configService: configService,
|
||||
client: wc,
|
||||
logger: l,
|
||||
p: ps,
|
||||
@@ -71,7 +71,7 @@ namespace Jackett.Indexers
|
||||
|
||||
}
|
||||
|
||||
public async Task<IndexerConfigurationStatus> ApplyConfiguration(JToken configJson)
|
||||
public override async Task<IndexerConfigurationStatus> ApplyConfiguration(JToken configJson)
|
||||
{
|
||||
LoadValuesFromJson(configJson);
|
||||
|
||||
@@ -92,7 +92,7 @@ namespace Jackett.Indexers
|
||||
return IndexerConfigurationStatus.RequiresTesting;
|
||||
}
|
||||
|
||||
public async Task<IEnumerable<ReleaseInfo>> PerformQuery(TorznabQuery query)
|
||||
protected override async Task<IEnumerable<ReleaseInfo>> PerformQuery(TorznabQuery query)
|
||||
{
|
||||
var releases = new List<ReleaseInfo>();
|
||||
var searchUrl = BrowsePage;
|
||||
|
||||
@@ -1,151 +1,122 @@
|
||||
using System;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using System.Web;
|
||||
using CsQuery;
|
||||
using Jackett.Models;
|
||||
using Jackett.Models.IndexerConfig;
|
||||
using Jackett.Services;
|
||||
using Jackett.Utils;
|
||||
using Jackett.Utils.Clients;
|
||||
using Newtonsoft.Json.Linq;
|
||||
using NLog;
|
||||
|
||||
namespace Jackett.Indexers.Meta
|
||||
{
|
||||
public class ImdbResolver {
|
||||
public ImdbResolver(IWebClient webClient) {
|
||||
WebClient = webClient;
|
||||
}
|
||||
|
||||
public async Task<IEnumerable<string>> GetAllTitles(string imdbId) {
|
||||
if (!imdbId.StartsWith("tt"))
|
||||
imdbId = "tt" + imdbId;
|
||||
var request = new WebRequest("http://www.imdb.com/title/" + imdbId + "/releaseinfo");
|
||||
var result = await WebClient.GetString(request);
|
||||
|
||||
CQ dom = result.Content;
|
||||
|
||||
var mainTitle = dom["h3[itemprop=name]"].Find("a")[0].InnerHTML.Replace("\"", "");
|
||||
|
||||
var akas = dom["table#akas"].Find("tbody").Find("tr");
|
||||
var titleList = new List<string>();
|
||||
titleList.Add(mainTitle);
|
||||
foreach (var row in akas) {
|
||||
string title = row.FirstElementChild.InnerHTML;
|
||||
if (title == "(original title)" || title == "")
|
||||
titleList.Add(HttpUtility.HtmlDecode(row.FirstElementChild.NextElementSibling.InnerHTML));
|
||||
}
|
||||
|
||||
return titleList;
|
||||
}
|
||||
|
||||
private IWebClient WebClient;
|
||||
}
|
||||
|
||||
public abstract class BaseMetaIndexer : BaseIndexer, IIndexer
|
||||
public abstract class BaseMetaIndexer : BaseWebIndexer
|
||||
{
|
||||
protected BaseMetaIndexer(string name, string description, IIndexerManagerService manager, IWebClient webClient, Logger logger, ConfigurationData configData, IProtectionService p, Func<IIndexer, bool> filter)
|
||||
: base(name, "http://127.0.0.1/", description, manager, webClient, logger, configData, p, null, null)
|
||||
protected BaseMetaIndexer(string name, string description, IFallbackStrategyProvider fallbackStrategyProvider, IResultFilterProvider resultFilterProvider, IIndexerConfigurationService configService, IWebClient webClient, Logger logger, ConfigurationData configData, IProtectionService p, Func<IIndexer, bool> filter)
|
||||
: base(name, "http://127.0.0.1/", description, configService, webClient, logger, configData, p, null, null)
|
||||
{
|
||||
filterFunc = filter;
|
||||
this.fallbackStrategyProvider = fallbackStrategyProvider;
|
||||
this.resultFilterProvider = resultFilterProvider;
|
||||
}
|
||||
|
||||
public Task<IndexerConfigurationStatus> ApplyConfiguration(JToken configJson)
|
||||
public override Task<IndexerConfigurationStatus> ApplyConfiguration(JToken configJson)
|
||||
{
|
||||
return Task.FromResult(IndexerConfigurationStatus.Completed);
|
||||
}
|
||||
|
||||
public virtual async Task<IEnumerable<ReleaseInfo>> PerformQuery(TorznabQuery query)
|
||||
public override async Task<IEnumerable<ReleaseInfo>> ResultsForQuery(TorznabQuery query)
|
||||
{
|
||||
IEnumerable<Task<IEnumerable<ReleaseInfo>>> tasks = Indexers.Where(i => i.CanHandleQuery(query)).Select(i => i.PerformQuery(query)).ToList(); // explicit conversion to List to execute LINQ query
|
||||
if (!CanHandleQuery(query))
|
||||
return new ReleaseInfo[0];
|
||||
var results = await PerformQuery(query);
|
||||
var correctedResults = results.Select(r =>
|
||||
{
|
||||
if (r.PublishDate > DateTime.Now)
|
||||
r.PublishDate = DateTime.Now;
|
||||
return r;
|
||||
});
|
||||
|
||||
bool needFallback = query.IsImdbQuery;
|
||||
IEnumerable<string> fallbackTitles = null;
|
||||
if (needFallback) {
|
||||
var imdb = new ImdbResolver(webclient);
|
||||
fallbackTitles = await imdb.GetAllTitles(query.ImdbID);
|
||||
var fallbackQueries = fallbackTitles.Select(t => query.CreateFallback(t));
|
||||
var backupTasks = fallbackQueries.SelectMany(q => Indexers.Where(i => !i.CanHandleQuery(query) && i.CanHandleQuery(q)).Select(i => i.PerformQuery(q.Clone())));
|
||||
tasks = tasks.Concat(backupTasks.ToList()); // explicit conversion to List to execute LINQ query
|
||||
}
|
||||
return correctedResults;
|
||||
}
|
||||
|
||||
var aggregateTask = Task.WhenAll<IEnumerable<ReleaseInfo>>(tasks);
|
||||
try {
|
||||
protected override async Task<IEnumerable<ReleaseInfo>> PerformQuery(TorznabQuery query)
|
||||
{
|
||||
var indexers = validIndexers;
|
||||
IEnumerable<Task<IEnumerable<ReleaseInfo>>> supportedTasks = indexers.Where(i => i.CanHandleQuery(query)).Select(i => i.ResultsForQuery(query)).ToList(); // explicit conversion to List to execute LINQ query
|
||||
|
||||
var fallbackStrategies = fallbackStrategyProvider.FallbackStrategiesForQuery(query);
|
||||
var fallbackQueries = fallbackStrategies.Select(async f => await f.FallbackQueries()).SelectMany(t => t.Result);
|
||||
var fallbackTasks = fallbackQueries.SelectMany(q => indexers.Where(i => !i.CanHandleQuery(query) && i.CanHandleQuery(q)).Select(i => i.ResultsForQuery(q.Clone())));
|
||||
var tasks = supportedTasks.Concat(fallbackTasks.ToList()); // explicit conversion to List to execute LINQ query
|
||||
|
||||
// When there are many indexers used by a metaindexer querying each and every one of them can take very very
|
||||
// long. This may result in a problem especially with Sonarr, which does consecutive searches when searching
|
||||
// for a season. Also, there might be indexers that do not support fast consecutive searches.
|
||||
// Therefore doing a season search in Sonarr might take for more than 90 seconds (approx. timeout in Sonarr)
|
||||
// which will mark Jackett as unresponsive (and therefore deactivated).
|
||||
// Although that 40 second is just an arbitrary number, doing a 40 second timeout is acceptable since an API
|
||||
// not responding for 40 second.. well, no one should really use that.
|
||||
// I hope in the future these queries will speed up (using caching or some other magic), however until then
|
||||
// just stick with a timeout.
|
||||
var aggregateTask = tasks.Until(TimeSpan.FromSeconds(40));
|
||||
|
||||
try
|
||||
{
|
||||
await aggregateTask;
|
||||
} catch {
|
||||
}
|
||||
catch
|
||||
{
|
||||
logger.Error(aggregateTask.Exception, "Error during request in metaindexer " + ID);
|
||||
}
|
||||
|
||||
var unorderedResult = tasks.Where(x => x.Status == TaskStatus.RanToCompletion).SelectMany(x => x.Result);
|
||||
var orderedResult = unorderedResult.Where(r => {
|
||||
var normalizedTitles = fallbackTitles.Concat(fallbackTitles.Select(t => t.Replace(' ', '.').Replace(":", ""))).Select(t => t.ToLowerInvariant());
|
||||
foreach (var title in normalizedTitles) {
|
||||
if (r.Title.ToLowerInvariant().Contains(title))
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}).OrderByDescending(r => r.Gain);
|
||||
|
||||
var filteredResult = orderedResult.Where(r => {
|
||||
if (r.Imdb != null) {
|
||||
try {
|
||||
return Int64.Parse(query.ImdbID.Select(c => char.IsDigit(c)).ToString()) == r.Imdb;
|
||||
} catch {
|
||||
// Cannot safely determine whether result is what we
|
||||
// wanted, so let's just leave it alone...
|
||||
}
|
||||
}
|
||||
return true;
|
||||
});
|
||||
var unorderedResult = aggregateTask.Result.Flatten();
|
||||
var resultFilters = resultFilterProvider.FiltersForQuery(query);
|
||||
var filteredResults = resultFilters.Select(async f => await f.FilterResults(unorderedResult)).SelectMany(t => t.Result);
|
||||
var uniqueFilteredResults = filteredResults.Distinct();
|
||||
var orderedResults = uniqueFilteredResults.OrderByDescending(r => r.Gain);
|
||||
// Limiting the response size might be interesting for use-cases where there are
|
||||
// tons of trackers configured in Jackett. For now just use the limit param if
|
||||
// someone wants to do that.
|
||||
IEnumerable<ReleaseInfo> result = filteredResult;
|
||||
IEnumerable<ReleaseInfo> result = orderedResults;
|
||||
if (query.Limit > 0)
|
||||
result = result.Take(query.Limit);
|
||||
return result;
|
||||
}
|
||||
|
||||
public override Uri UncleanLink(Uri link)
|
||||
public override TorznabCapabilities TorznabCaps
|
||||
{
|
||||
var indexer = GetOriginalIndexerForLink(link);
|
||||
if (indexer != null)
|
||||
return indexer.UncleanLink(link);
|
||||
|
||||
return base.UncleanLink(link);
|
||||
get
|
||||
{
|
||||
return validIndexers.Select(i => i.TorznabCaps).Aggregate(new TorznabCapabilities(), TorznabCapabilities.Concat);
|
||||
}
|
||||
}
|
||||
|
||||
public override Task<byte[]> Download(Uri link)
|
||||
public override bool IsConfigured
|
||||
{
|
||||
var indexer = GetOriginalIndexerForLink(link);
|
||||
if (indexer != null)
|
||||
return indexer.Download(link);
|
||||
|
||||
return base.Download(link);
|
||||
get
|
||||
{
|
||||
return Indexers != null;
|
||||
}
|
||||
}
|
||||
|
||||
private IIndexer GetOriginalIndexerForLink(Uri link)
|
||||
private IEnumerable<IIndexer> validIndexers
|
||||
{
|
||||
var prefix = string.Format("{0}://{1}", link.Scheme, link.Host);
|
||||
var validIndexers = Indexers.Where(i => i.SiteLink.StartsWith(prefix, StringComparison.CurrentCulture));
|
||||
if (validIndexers.Count() > 0)
|
||||
return validIndexers.First();
|
||||
get
|
||||
{
|
||||
if (Indexers == null)
|
||||
return null;
|
||||
|
||||
return null;
|
||||
return Indexers.Where(i => i.IsConfigured && filterFunc(i));
|
||||
}
|
||||
}
|
||||
|
||||
public IEnumerable<IIndexer> Indexers;
|
||||
|
||||
private Func<IIndexer, bool> filterFunc;
|
||||
private IEnumerable<IIndexer> indexers;
|
||||
public IEnumerable<IIndexer> Indexers {
|
||||
get {
|
||||
return indexers;
|
||||
}
|
||||
set {
|
||||
indexers = value.Where(i => i.IsConfigured && filterFunc(i));
|
||||
TorznabCaps = value.Select(i => i.TorznabCaps).Aggregate(new TorznabCapabilities(), TorznabCapabilities.Concat); ;
|
||||
IsConfigured = true;
|
||||
}
|
||||
}
|
||||
private IFallbackStrategyProvider fallbackStrategyProvider;
|
||||
private IResultFilterProvider resultFilterProvider;
|
||||
}
|
||||
}
|
||||
|
||||
77
src/Jackett/Indexers/Meta/Fallbacks.cs
Normal file
77
src/Jackett/Indexers/Meta/Fallbacks.cs
Normal file
@@ -0,0 +1,77 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using Jackett.Models;
|
||||
using Jackett.Services;
|
||||
using Jackett.Utils;
|
||||
|
||||
namespace Jackett.Indexers.Meta
|
||||
{
|
||||
public interface IFallbackStrategy
|
||||
{
|
||||
Task<IEnumerable<TorznabQuery>> FallbackQueries();
|
||||
}
|
||||
|
||||
public interface IFallbackStrategyProvider
|
||||
{
|
||||
IEnumerable<IFallbackStrategy> FallbackStrategiesForQuery(TorznabQuery query);
|
||||
}
|
||||
|
||||
public class NoFallbackStrategy : IFallbackStrategy
|
||||
{
|
||||
public Task<IEnumerable<TorznabQuery>> FallbackQueries()
|
||||
{
|
||||
return Task.FromResult<IEnumerable<TorznabQuery>>(new List<TorznabQuery>());
|
||||
}
|
||||
}
|
||||
|
||||
public class NoFallbackStrategyProvider : IFallbackStrategyProvider
|
||||
{
|
||||
public IEnumerable<IFallbackStrategy> FallbackStrategiesForQuery(TorznabQuery query)
|
||||
{
|
||||
return (new NoFallbackStrategy()).ToEnumerable();
|
||||
}
|
||||
}
|
||||
|
||||
public class ImdbFallbackStrategy : IFallbackStrategy
|
||||
{
|
||||
public ImdbFallbackStrategy(IImdbResolver resolver, TorznabQuery query)
|
||||
{
|
||||
this.resolver = resolver;
|
||||
this.titles = null;
|
||||
this.query = query;
|
||||
}
|
||||
|
||||
public async Task<IEnumerable<TorznabQuery>> FallbackQueries()
|
||||
{
|
||||
if (titles == null)
|
||||
titles = (await resolver.MovieForId(query.ImdbID.ToNonNull())).Title.ToEnumerable();
|
||||
return titles.Select(t => query.CreateFallback(t));
|
||||
}
|
||||
|
||||
private IImdbResolver resolver;
|
||||
private IEnumerable<string> titles;
|
||||
private TorznabQuery query;
|
||||
}
|
||||
|
||||
public class ImdbFallbackStrategyProvider : IFallbackStrategyProvider
|
||||
{
|
||||
public ImdbFallbackStrategyProvider(IImdbResolver resolver)
|
||||
{
|
||||
this.resolver = resolver;
|
||||
}
|
||||
|
||||
public IEnumerable<IFallbackStrategy> FallbackStrategiesForQuery(TorznabQuery query)
|
||||
{
|
||||
var result = new List<IFallbackStrategy>();
|
||||
if (!query.IsImdbQuery)
|
||||
result.Add(new NoFallbackStrategy());
|
||||
else
|
||||
result.Add(new ImdbFallbackStrategy(resolver, query));
|
||||
return result;
|
||||
}
|
||||
|
||||
private IImdbResolver resolver;
|
||||
}
|
||||
}
|
||||
@@ -14,8 +14,15 @@ namespace Jackett.Indexers.Meta
|
||||
{
|
||||
class AggregateIndexer : BaseMetaIndexer
|
||||
{
|
||||
public AggregateIndexer(IIndexerManagerService i, IWebClient wc, Logger l, IProtectionService ps)
|
||||
: base("AggregateSearch", "This feed includes all configured trackers", i, wc, l, new ConfigurationData(), ps, x => true)
|
||||
public override string ID
|
||||
{
|
||||
get
|
||||
{
|
||||
return "all";
|
||||
}
|
||||
}
|
||||
public AggregateIndexer(IFallbackStrategyProvider fallbackStrategyProvider, IResultFilterProvider resultFilterProvider, IIndexerConfigurationService configService, IWebClient wc, Logger l, IProtectionService ps)
|
||||
: base("AggregateSearch", "This feed includes all configured trackers", fallbackStrategyProvider, resultFilterProvider, configService, wc, l, new ConfigurationData(), ps, x => true)
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
124
src/Jackett/Indexers/Meta/ResultFilters.cs
Normal file
124
src/Jackett/Indexers/Meta/ResultFilters.cs
Normal file
@@ -0,0 +1,124 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using Jackett.Models;
|
||||
using Jackett.Services;
|
||||
using Jackett.Utils;
|
||||
|
||||
namespace Jackett.Indexers.Meta
|
||||
{
|
||||
public interface IResultFilter
|
||||
{
|
||||
Task<IEnumerable<ReleaseInfo>> FilterResults(IEnumerable<ReleaseInfo> results);
|
||||
}
|
||||
|
||||
public interface IResultFilterProvider
|
||||
{
|
||||
IEnumerable<IResultFilter> FiltersForQuery(TorznabQuery query);
|
||||
}
|
||||
|
||||
public class ImdbTitleResultFilter : IResultFilter
|
||||
{
|
||||
public ImdbTitleResultFilter(IImdbResolver resolver, TorznabQuery query)
|
||||
{
|
||||
this.resolver = resolver;
|
||||
this.query = query;
|
||||
}
|
||||
|
||||
public async Task<IEnumerable<ReleaseInfo>> FilterResults(IEnumerable<ReleaseInfo> results)
|
||||
{
|
||||
long? imdbId = null;
|
||||
try {
|
||||
var normalizedImdbId = String.Concat(query.ImdbID.Where(c => char.IsDigit(c)));
|
||||
imdbId = Int64.Parse(normalizedImdbId);
|
||||
} catch {
|
||||
}
|
||||
|
||||
IEnumerable<ReleaseInfo> perfectResults;
|
||||
IEnumerable<ReleaseInfo> wrongResults;
|
||||
|
||||
if (imdbId != null) {
|
||||
var resultsWithImdbId = results.Where(r => r.Imdb != null);
|
||||
wrongResults = resultsWithImdbId.Where(r => r.Imdb != imdbId);
|
||||
perfectResults = resultsWithImdbId.Where(r => r.Imdb == imdbId);
|
||||
} else {
|
||||
wrongResults = new ReleaseInfo[] { };
|
||||
perfectResults = new ReleaseInfo[] { };
|
||||
}
|
||||
|
||||
var remainingResults = results.Except(wrongResults).Except(perfectResults);
|
||||
|
||||
var titles = (await resolver.MovieForId(query.ImdbID.ToNonNull())).Title.ToEnumerable();
|
||||
var strippedTitles = titles.Select(t => RemoveSpecialChars(t));
|
||||
var normalizedTitles = strippedTitles.SelectMany(t => GenerateTitleVariants(t));
|
||||
|
||||
var titleFilteredResults = remainingResults.Where(r => {
|
||||
// TODO Make it possible to configure case insensitivity
|
||||
var containsAnyTitle = normalizedTitles.Select(t => r.Title.ToLowerInvariant().Contains(t.ToLowerInvariant()));
|
||||
var isProbablyValidResult = containsAnyTitle.Any(b => b);
|
||||
return isProbablyValidResult;
|
||||
});
|
||||
|
||||
var filteredResults = perfectResults.Concat(titleFilteredResults).Distinct();
|
||||
return filteredResults;
|
||||
}
|
||||
|
||||
private static string RemoveSpecialChars(string title)
|
||||
{
|
||||
// TODO improve character replacement with invalid chars
|
||||
return title.Replace(":", "");
|
||||
}
|
||||
|
||||
private static IEnumerable<string> GenerateTitleVariants(string title)
|
||||
{
|
||||
var delimiterVariants = new char[] { '.', '_' };
|
||||
var result = new List<string>();
|
||||
var replacedTitles = delimiterVariants.Select(c => title.Replace(' ', c));
|
||||
|
||||
result.Add(title);
|
||||
result.AddRange(replacedTitles);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
private IImdbResolver resolver;
|
||||
private TorznabQuery query;
|
||||
}
|
||||
|
||||
public class NoFilter : IResultFilter
|
||||
{
|
||||
public Task<IEnumerable<ReleaseInfo>> FilterResults(IEnumerable<ReleaseInfo> results)
|
||||
{
|
||||
return Task.FromResult(results);
|
||||
}
|
||||
}
|
||||
|
||||
public class NoResultFilterProvider : IResultFilterProvider
|
||||
{
|
||||
public IEnumerable<IResultFilter> FiltersForQuery(TorznabQuery query)
|
||||
{
|
||||
return (new NoFilter()).ToEnumerable();
|
||||
}
|
||||
}
|
||||
|
||||
public class ImdbTitleResultFilterProvider : IResultFilterProvider
|
||||
{
|
||||
public ImdbTitleResultFilterProvider(IImdbResolver resolver)
|
||||
{
|
||||
this.resolver = resolver;
|
||||
}
|
||||
|
||||
public IEnumerable<IResultFilter> FiltersForQuery(TorznabQuery query)
|
||||
{
|
||||
IResultFilter filter = null;
|
||||
if (!query.IsImdbQuery)
|
||||
filter = new NoFilter();
|
||||
else
|
||||
filter = new ImdbTitleResultFilter(resolver, query);
|
||||
return filter.ToEnumerable();
|
||||
}
|
||||
|
||||
private IImdbResolver resolver;
|
||||
}
|
||||
}
|
||||
@@ -19,7 +19,7 @@ using Jackett.Utils;
|
||||
|
||||
namespace Jackett.Indexers
|
||||
{
|
||||
public class MoreThanTV : BaseIndexer, IIndexer
|
||||
public class MoreThanTV : BaseWebIndexer
|
||||
{
|
||||
private string LoginUrl => SiteLink + "login.php";
|
||||
private string SearchUrl => SiteLink + "ajax.php?action=browse&searchstr=";
|
||||
@@ -28,13 +28,13 @@ namespace Jackett.Indexers
|
||||
|
||||
private ConfigurationDataBasicLogin ConfigData => (ConfigurationDataBasicLogin) configData;
|
||||
|
||||
public MoreThanTV(IIndexerManagerService i, IWebClient c, Logger l, IProtectionService ps)
|
||||
public MoreThanTV(IIndexerConfigurationService configService, IWebClient c, Logger l, IProtectionService ps)
|
||||
: base(name: "MoreThanTV",
|
||||
description: "ROMANIAN Private Torrent Tracker for TV / MOVIES, and the internal tracker for the release group DRACULA.",
|
||||
link: "https://www.morethan.tv/",
|
||||
caps: new TorznabCapabilities(TorznabCatType.TV,
|
||||
TorznabCatType.Movies),
|
||||
manager: i,
|
||||
configService: configService,
|
||||
client: c,
|
||||
logger: l,
|
||||
p: ps,
|
||||
@@ -45,7 +45,7 @@ namespace Jackett.Indexers
|
||||
Type = "private";
|
||||
}
|
||||
|
||||
public async Task<IndexerConfigurationStatus> ApplyConfiguration(JToken configJson)
|
||||
public override async Task<IndexerConfigurationStatus> ApplyConfiguration(JToken configJson)
|
||||
{
|
||||
LoadValuesFromJson(configJson);
|
||||
var pairs = new Dictionary<string, string> {
|
||||
@@ -69,7 +69,7 @@ namespace Jackett.Indexers
|
||||
return IndexerConfigurationStatus.RequiresTesting;
|
||||
}
|
||||
|
||||
public async Task<IEnumerable<ReleaseInfo>> PerformQuery(TorznabQuery query)
|
||||
protected override async Task<IEnumerable<ReleaseInfo>> PerformQuery(TorznabQuery query)
|
||||
{
|
||||
var isTv = TorznabCatType.QueryContainsParentCategory(query.Categories, new List<int> { TorznabCatType.TV.ID });
|
||||
var releases = new List<ReleaseInfo>();
|
||||
@@ -78,16 +78,13 @@ namespace Jackett.Indexers
|
||||
|
||||
await GetReleases(releases, query, searchQuerySingleEpisodes);
|
||||
|
||||
// Search for torrent groups
|
||||
if (isTv)
|
||||
// Search for torrent groups (complete seasons)
|
||||
var seasonMatch = new Regex(@".*\s[Ss]{1}\d{2}$").Match(query.GetQueryString());
|
||||
if (seasonMatch.Success)
|
||||
{
|
||||
var seasonMatch = new Regex(@".*\s[Ss]{1}\d{2}").Match(query.GetQueryString());
|
||||
if (seasonMatch.Success)
|
||||
{
|
||||
var newSearchQuery = Regex.Replace(searchQuery, @"[Ss]{1}\d{2}", $"Season {query.Season}");
|
||||
var newSearchQuery = Regex.Replace(searchQuery, @"[Ss]{1}\d{2}", $"Season {query.Season}");
|
||||
|
||||
await GetReleases(releases, query, newSearchQuery);
|
||||
}
|
||||
await GetReleases(releases, query, newSearchQuery);
|
||||
}
|
||||
|
||||
return releases;
|
||||
@@ -231,8 +228,9 @@ namespace Jackett.Indexers
|
||||
torrentId = torrentId.Split('#')[0];
|
||||
|
||||
var size = ReleaseInfo.GetBytes(torrentData[0].TextContent);
|
||||
var grabs = int.Parse(torrentData[1].TextContent);
|
||||
var seeders = int.Parse(torrentData[2].TextContent);
|
||||
var grabs = int.Parse(torrentData[1].TextContent, NumberStyles.AllowThousands, CultureInfo.InvariantCulture);
|
||||
var seeders = int.Parse(torrentData[2].TextContent, NumberStyles.AllowThousands, CultureInfo.InvariantCulture);
|
||||
var leechers = int.Parse(torrentData[3].TextContent, NumberStyles.AllowThousands, CultureInfo.InvariantCulture);
|
||||
var guid = new Uri(GuidUrl + torrentId);
|
||||
|
||||
// Build releaseinfo
|
||||
@@ -243,7 +241,7 @@ namespace Jackett.Indexers
|
||||
Link = new Uri(DownloadUrl + torrentId),
|
||||
PublishDate = publishDate,
|
||||
Seeders = seeders,
|
||||
Peers = seeders,
|
||||
Peers = seeders + leechers,
|
||||
Files = files,
|
||||
Size = size,
|
||||
Grabs = grabs,
|
||||
|
||||
@@ -21,7 +21,7 @@ using System.Globalization;
|
||||
|
||||
namespace Jackett.Indexers
|
||||
{
|
||||
public class Myanonamouse : BaseIndexer, IIndexer
|
||||
public class Myanonamouse : BaseWebIndexer
|
||||
{
|
||||
private string LoginUrl { get { return SiteLink + "takelogin.php"; } }
|
||||
private string SearchUrl { get { return SiteLink + "tor/js/loadSearch2.php"; } }
|
||||
@@ -32,7 +32,7 @@ namespace Jackett.Indexers
|
||||
set { base.configData = value; }
|
||||
}
|
||||
|
||||
public Myanonamouse(IIndexerManagerService i, IWebClient c, Logger l, IProtectionService ps)
|
||||
public Myanonamouse(IIndexerConfigurationService configService, IWebClient c, Logger l, IProtectionService ps)
|
||||
: base(name: "MyAnonamouse",
|
||||
description: "Friendliness, Warmth and Sharing",
|
||||
link: "https://www.myanonamouse.net/",
|
||||
@@ -42,7 +42,7 @@ namespace Jackett.Indexers
|
||||
TorznabCatType.BooksEbook,
|
||||
TorznabCatType.BooksMagazines,
|
||||
TorznabCatType.BooksTechnical),
|
||||
manager: i,
|
||||
configService: configService,
|
||||
client: c,
|
||||
logger: l,
|
||||
p: ps,
|
||||
@@ -131,7 +131,7 @@ namespace Jackett.Indexers
|
||||
|
||||
}
|
||||
|
||||
public async Task<IndexerConfigurationStatus> ApplyConfiguration(JToken configJson)
|
||||
public override async Task<IndexerConfigurationStatus> ApplyConfiguration(JToken configJson)
|
||||
{
|
||||
LoadValuesFromJson(configJson);
|
||||
var pairs = new Dictionary<string, string> {
|
||||
@@ -153,7 +153,7 @@ namespace Jackett.Indexers
|
||||
return IndexerConfigurationStatus.RequiresTesting;
|
||||
}
|
||||
|
||||
public async Task<IEnumerable<ReleaseInfo>> PerformQuery(TorznabQuery query)
|
||||
protected override async Task<IEnumerable<ReleaseInfo>> PerformQuery(TorznabQuery query)
|
||||
{
|
||||
var releases = new List<ReleaseInfo>();
|
||||
|
||||
|
||||
@@ -16,7 +16,7 @@ using System.Text.RegularExpressions;
|
||||
|
||||
namespace Jackett.Indexers
|
||||
{
|
||||
public class NCore : BaseIndexer, IIndexer
|
||||
public class NCore : BaseWebIndexer
|
||||
{
|
||||
private string LoginUrl { get { return SiteLink + "login.php"; } }
|
||||
private string SearchUrl { get { return SiteLink + "torrents.php"; } }
|
||||
@@ -28,12 +28,12 @@ namespace Jackett.Indexers
|
||||
set { base.configData = value; }
|
||||
}
|
||||
|
||||
public NCore(IIndexerManagerService i, IWebClient wc, Logger l, IProtectionService ps)
|
||||
public NCore(IIndexerConfigurationService configService, IWebClient wc, Logger l, IProtectionService ps)
|
||||
: base(name: "nCore",
|
||||
description: "A Hungarian private torrent site.",
|
||||
link: "https://ncore.cc/",
|
||||
caps: new TorznabCapabilities(),
|
||||
manager: i,
|
||||
configService: configService,
|
||||
client: wc,
|
||||
logger: l,
|
||||
p: ps,
|
||||
@@ -82,7 +82,7 @@ namespace Jackett.Indexers
|
||||
AddCategoryMapping("ebook", TorznabCatType.Books , "Könyv eBook/EN");
|
||||
}
|
||||
|
||||
public async Task<IndexerConfigurationStatus> ApplyConfiguration(JToken configJson)
|
||||
public override async Task<IndexerConfigurationStatus> ApplyConfiguration(JToken configJson)
|
||||
{
|
||||
LoadValuesFromJson(configJson);
|
||||
|
||||
@@ -99,6 +99,11 @@ namespace Jackett.Indexers
|
||||
{ "submit", "Access!" }
|
||||
};
|
||||
|
||||
if (!string.IsNullOrEmpty(configData.TwoFactor.Value))
|
||||
{
|
||||
pairs.Add("2factor", configData.TwoFactor.Value);
|
||||
}
|
||||
|
||||
var result = await RequestLoginAndFollowRedirect(LoginUrl, pairs, loginPage.Cookies, true, referer: SiteLink);
|
||||
await ConfigureIfOK(result.Cookies, result.Content != null && result.Content.Contains("profile.php"), () =>
|
||||
{
|
||||
@@ -112,7 +117,7 @@ namespace Jackett.Indexers
|
||||
return IndexerConfigurationStatus.RequiresTesting;
|
||||
}
|
||||
|
||||
public async Task<IEnumerable<ReleaseInfo>> PerformQuery(TorznabQuery query)
|
||||
protected override async Task<IEnumerable<ReleaseInfo>> PerformQuery(TorznabQuery query)
|
||||
{
|
||||
var releases = new List<ReleaseInfo>();
|
||||
var searchString = query.GetQueryString();
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user