Compare commits

..

114 Commits

Author SHA1 Message Date
chibidev
c2f11306b1 Fixing SupportsCategories (or at least making it better)
I cannot wrap my head around why it has started to fail now as it was
faulty from the beginning... Anyway, SupportsCategories didn't really
take sub categories into account, which is bad.

- Potentially fixes #1654 and #1656
2017-08-14 10:38:51 +02:00
JigSaw
07a0c2c828 fix(indexers): fixed wihd, added ssl support and misc things (#1666)
* docs(config): add warning on wihd config to use classic view only

* refactor(wihd): refactor indexer, optimized dev mode, added freeleech and SSL

* fix(indexers): removed T411 orginal tracker

closed tracker by gov

* refactor(clean): removed old orphan config files

* docs(readme): updated for wihd
2017-08-14 02:10:50 +02:00
JigSaw
28eaa637df fix(indexers): Abn Indexer, Added 4K to Xthor (#1665)
* feat(xthor): added 4K category

* fix(abn): optimized abnormal indexer, fixed scraping for pending and dev mode
2017-08-14 00:15:11 +02:00
JigSaw
8707e6b2e9 fix(indexers): fixed and optimized xthor indexer
* feat(utils): added sha1 hash function and refactored md5 hash function

* fix(indexers): now use cross plateform path building for dev mode

* fix(indexers): fix output log of xthor for dev mode

* feat(release): added ToString method

* refactor(dev): optimized dev mode

* style(clean): cleanup code

* feat(indexer): added tmdb info to releases

* fix(cat): enabled categories on xthor
2017-08-13 20:49:03 +02:00
chibidev
7a8d83b693 Allow anonymous access to results - probably fixes #1654 2017-08-13 02:23:39 +02:00
chibidev
403c0ef7e4 Correct API path in JS client 2017-08-12 20:12:30 +02:00
chibidev
3374c14311 Fix download handling in AggregateIndexer 2017-08-12 11:12:02 +02:00
chibidev
9eade51d89 Increase Sonarr compatbility 2017-08-12 10:46:12 +02:00
David Torosyan
bc9e4a30cb Fix thepiratebay.xml for General Hospital (#1647)
* Fix thepiratebay.xml for General Hospital

* Add comment
2017-08-12 16:19:41 +10:00
flightlevel
0103c48c97 Fix settings update from UI (#1655)
https://github.com/Jackett/Jackett/issues/1654
2017-08-12 11:12:56 +10:00
chibidev
29ecf8a584 "Fix" ShowRSS category handling 2017-08-12 01:59:50 +02:00
chibidev
eec07bc5b8 Fix manual search - append apikey 2017-08-12 01:04:30 +02:00
chibidev
bf23878477 Manual search URL fix - hash will reset now 2017-08-12 01:04:07 +02:00
chibidev
043085e8f3 Fix torznab capability API 2017-08-12 00:30:43 +02:00
chibidev
39e612d60c Move debug data to appropriate log level 2017-08-11 23:55:55 +02:00
chibidev
6f8b1b749d Fix default torznab query 2017-08-11 23:55:40 +02:00
kaso17
439e9296f9 dummy commit 2017-08-11 19:10:48 +02:00
kaso17
1332b49370 UI: fix searchCategory dropdown 2017-08-11 18:55:47 +02:00
kaso17
33a7db5ec4 NextTorrent: update default URLs 2017-08-11 18:46:32 +02:00
kaso17
2075e914eb fix /api route names 2017-08-11 18:34:54 +02:00
kaso17
758ad91a55 add /api legacy routes 2017-08-11 18:27:45 +02:00
kaso17
e9b604d3c4 fix potato API 2017-08-11 18:13:22 +02:00
kaso17
51aa4f35bd AnimeBytes: stop emulating browser 2017-08-11 16:53:49 +02:00
kaso17
fc55882f16 IndexerManagerService: create own IWebClient instance for each indexer 2017-08-11 16:53:27 +02:00
kaso17
d03cbefa57 IWebClient: add EmulateBrowser flag 2017-08-11 16:52:58 +02:00
kaso17
e079c90535 Torrent Downloads: don't ask for user/password and fix empty search 2017-08-11 16:05:19 +02:00
kaso17
8591add0bf FileList: use full title if available 2017-08-11 15:57:55 +02:00
kaso17
fbbe4f9c45 FileList: don't use FL token DL link 2017-08-11 15:48:34 +02:00
kaso17
289c5cd24f encrypt original path in download links and move apikey to parameters 2017-08-11 15:14:40 +02:00
flightlevel
84a45737d3 Revert "Fixes #1606 " (#1649)
* Revert "Pretome: Attempted parsing fix (#1648)"

This reverts commit d083cf774a.

* Revert "t411 v2 (#1620)"

This reverts commit f2ce167bbf.

* Revert "Fixes #1606  (#1625)"

This reverts commit 4e04bbbcf4.
2017-08-11 22:29:15 +10:00
flightlevel
d083cf774a Pretome: Attempted parsing fix (#1648)
* Pretome: Attempted parsing fix
2017-08-11 21:54:44 +10:00
Francis Noel
f2ce167bbf t411 v2 (#1620)
* New nextorrent URL

Change url to : http://www.nextorrent.cx
Fix search url

* new cpasbien url

Change to : http://cpabien.cc

* Add zetorrents tracker

* t411 v2 + remove maniatorrent

* Update Jackett.Updater (remove maniatorrent)

* fix the formatting

Change TAB with SPACE
2017-08-11 21:45:49 +10:00
Benoît Sauvère
4e04bbbcf4 Fixes #1606 (#1625)
* Fixed the TorrentPotato requests with the indexers that does not support imdbid requests

* Fixed the "CanHandleQuery" which was not working if we make the request only with an imdbid (and a valid OMDB key)

* Fixed the "CanHandleQuery" which was not working if we make the request only with an imdbid (and a valid OMDB key)

* Removed PotatoController.cs
2017-08-11 21:41:50 +10:00
garfield69
7f5d00e89f torrents-search.php -> search-torrents.php (#1642)
KAT seem to have changed the name of the search php
2017-08-10 22:03:41 +10:00
flightlevel
f777114644 Pretome: Attempted parsing fix (#1644) 2017-08-10 22:01:41 +10:00
flightlevel
7c63a6b8f2 ArcheTorrent: Update categories (#1643)
Fixes https://github.com/Jackett/Jackett/issues/1614
2017-08-10 21:51:59 +10:00
aurelien
f0140999bf 2 Fixes (#1637)
* lower case for search term

* fix thanksyou call before download torrent file
2017-08-10 08:23:19 +02:00
Khogniak
6f0a249503 YGG config update (#1635)
* YGG config update

* Followed gprime44 on seeder/leecher selector #1634
2017-08-10 08:21:12 +02:00
mueslo
2e79500f50 Improve season query logic for bB (#1632)
* Tweak BB indexer

Remove explicit tracker URL
Add tracker-specific season query

* Query for "S##" and "Season #" simultaneously on bB

* Fix tabs/spaces

* Rewrite title from "Season #" to "S##"

* Fix bug

Fix bug where searching for whole shows would rewrite Season # to S00, since no season was specified.

* Add new categories mappings

So that e.g. headphones can correctly query bB for music
2017-08-10 08:19:27 +02:00
chibidev
720b5971d3 Feature/new api (#1584)
* Introducing API v2

There were multiple inconsistencies in the old API and I have been
toying with the idea to replace it. This will suck for everyone who was
building on top of the Jackett API, however as it was probably too
painful to do so I'd say no one really tried.

Now API v2.0 should be much more constistent as it uses DTObjects, and
instead of manually constructing a json response it is handled by the
ASP.NET web api. It is much more RESTful than it was, proper GET
endpoints are introduced, and updating resources are now done via POST -
it might be improved by introducing other type of REST methods.

I know this sucks as completely breaks backward compatibility, however
it'll probably make it easier to maintain and build on top of in the
long run.

* Use DELETE method to unconfigure an indexer

* Remove debugging format from NLog

* Fixing an null exception

* Properly implementing IExceptionFilter interface

* Enable adding public indexers without configuration

* Fix missing manual search results

* Basic modularization of the JS API

* Introduce API versioning

* Fix redirects to the dashboard

* Cleaning up a little bit

* Revamping Torznab and Potato as well

* Move preconditions to FilterAttributes and simplify logic

* Remove legacy filtering... will move to IResultFilter

* Minor adjustment on results interface

* Use Interpolated strings in ResultsController

* DTO-ify

* Remove fallback logic from potato results

* DTO everywhere!!!

* DTO-ify everything!

* I hope this is my last piece of modification to this PR

* Remove test variables...

* Left out a couple conflicts... It's late
2017-08-08 17:02:16 +02:00
chibidev
dba63857e4 Improve results and fix download link on AnimeTosho 2017-08-08 09:27:21 +02:00
danmed
b05ee653d3 Update skytorrents.yml (#1626) 2017-08-08 01:19:04 +02:00
chibidev
84df60368c Improve response time of metaindexers 2017-08-07 22:33:23 +02:00
chibidev
76102ac171 Fix exception in BaseNewznabIndexer in some cases... Might not catch all of them. 2017-08-06 20:44:22 +02:00
Francis Noel
3c6e77a2ca Fix issue : #1615 (#1619)
* New nextorrent URL

Change url to : http://www.nextorrent.cx
Fix search url

* new cpasbien url

Change to : http://cpabien.cc

* Add zetorrents tracker
2017-08-06 10:41:53 +10:00
flightlevel
f6272032a6 MoreThanTv: Improve Number parsing (#1618)
Fixes #1616
2017-08-05 11:20:50 +10:00
Florentin Le Moal
41fb5e89b4 Fixed little typo (#1609) 2017-08-05 10:46:03 +10:00
Indrek Ardel
42c9967844 Use local time for dates in TorrentBytes (#1586)
TorrentBytes allows changing timezone in settings, which makes listed dates to be more likely local time than UTC.
2017-08-05 10:45:38 +10:00
chibidev
e740e2434d Fix selector on World of P2P 2017-07-29 14:02:04 +02:00
chibidev
057df28d1b Fixing a null exception when searching AnimeTosho
AnimeTosho failed to populate the Origin field of the results when
searching thereby creating an issue upon creating the proxy link. This
was mainly because an IEnumerable can contain a deferred LINQ query as
well (e.g. in the case of AnimeTosho a Select) that will re-execute
every single time. So when iterating over IEnumerables we cannot really
pose any assumption on what we are dealing with, so either explicitly
force an execution (e.g. via ToList), or use LINQ queries as well. Since
the second is probably more performant, let's stick with that.
2017-07-29 13:20:10 +02:00
Indrek Ardel
6fbc4b6904 Use only one request on Torrentbytes to search (#1587)
To get a larger number of results, users should increase the number on the website, similar to how it's implemented for TorrentLeech. Since TorrentBytes can be slow at times, making one large request will not be that much slower from one small request, but will nearly half the time taken compared to two smaller requests.
2017-07-27 23:49:20 +02:00
chibidev
cab608f0ec Adding the ability to launch a search from with a URL
You can now pass a parameter to the dashboard which will immediately pop
the search box and start searching for the results.
This should be what #1345 and #1577 wants, or at least a basic version
of it.
2017-07-23 02:08:19 +02:00
Ben Houshmand
f5592d04e2 Added yts.ag as an indexer. (#1579) 2017-07-23 01:12:12 +02:00
chibidev
5138496436 Display the master branch build status rather than any branch 2017-07-21 17:57:41 +02:00
chibidev
bcc1e5ff6c Fix OMDB key handling (#1581) 2017-07-21 17:56:29 +02:00
chibidev
7eb57d8e9a Minor fixes for Anime Tosho 2017-07-21 00:32:57 +02:00
chibidev
929c12ccc6 Implementing Anime Tosho - introducing feed based indexers 2017-07-20 23:55:42 +02:00
chibidev
1ba1b91b8e Very basic range check for port number 2017-07-19 22:25:01 +02:00
chibidev
d90a2613fc #1573 - Handle Potato IMDB queries without fallback 2017-07-19 19:31:07 +02:00
chibidev
0d25ed2921 #1575 - Fix invalid URL handling 2017-07-19 19:24:32 +02:00
thebluepotato
a07bffa773 Update KAT definition for real login test, error handling and indexer testing logic (#1569)
* Update KAT definition for real login test

* Add error handling to KAT definition's login

* Even better error handling...

Sorry for the inelegant successive commits

* Add better test behaviour for KAT

Added some logic so tests return results. Previously did a search with an empty string which would return no results. NB : it does test with "torrents.php?search=%22%22" but that's not an issue, the parameter is ignored on that page.
2017-07-19 19:11:29 +02:00
chibidev
f0da6ce247 #1535 - Using a real User-Agent with AnimeBytes 2017-07-16 13:24:14 +02:00
Guizzoni
32d0a8d703 Add "Torrent Downloads" tracker (#1563)
* Add "Torrent Downloads" tracker

Add "Torrent Downloads" tracker as requested on "Issue #1559"

* Update README.md

Added "Torrent Downloads" to the list.

* Update torrentdownloads.yml
2017-07-16 13:16:09 +02:00
kaso17
6e2087d5dc attempt to fix blackhole dirs with reverse proxies 2017-07-14 08:18:36 +02:00
Guizzoni
82f330f4af Add B2S-Share tracker (#1562)
* New definition for indexer "B2S-Share"

I made a new definition for this indexer.
B2S-Share is a general Brazilian tracker.

This tracker does not support multiple category search, so i left it searching in all categories.

* Update README.md

Added B2S-Share to the private tracker list.
2017-07-14 07:43:47 +02:00
Khogniak
14f86107e1 YGG Fix: Removed stage settings. Added https. Added login headers (just in case). (#1561) 2017-07-14 07:42:40 +02:00
Guizzoni
845faf9066 Add Manicomio Share tracker (#1558)
* New definition for indexer "Manicomio Share"

I made a new definition for this indexer.
Manicomio Share is a general Brazilian tracker.

A few known issues:
1 - The tracker does not show the "release date" unless i click on another button with a "onclick="expand(584941,event)"". So i left it without date.
2 - I wasn't able to configure the "login: post" method, since there's no dedicated login page. I used Cookies instead.

* Add Manicomio Share to Private Tracker list

Add Manicomio Share to Private Tracker list

* Updated manicomioshare.yml

Updated line 229 in manicomioshare.yml so it's more flexible in case the URL changes. (Thanks kaso17)

* Tracker doesn't support multiple categories search

Tracker doesn't support multiple categories search, removed the "range .Categories" line
2017-07-14 07:42:04 +02:00
chibidev
7c2b801ee9 Feature/remove autofac from indexer manager (#1549)
* Line endings...

* Remove Autofac and all its shenanigans from IndexerManager

I'm starting my warpath against Autofac. For the next couple PRs I want
to focus on refactoring things rather than creating new things. This
includes introducing LINQ extensions wherever possible as well as
removing Autofac/Automapper dependencies in classes (or any other
dependency, there's a great chance that most of the classes that use
Jackett.Services wouldn't need so many of them). All this is order to
boost performance and eventually reach testability. Can't stop, won't
stop.

* Remove unnecessary extension

* Modify test project

* As per @kaso17, iterating through iterator types rather than allocating them manually

* Cleaning up a little

* Adjusting interface in tests
2017-07-14 07:39:52 +02:00
kaso17
f96dca5653 Merge branch 'master' of https://github.com/Jackett/Jackett 2017-07-13 07:20:09 +02:00
kaso17
0e4e9f4253 AnimeTorrents: use IWebClient 2017-07-13 07:19:04 +02:00
chibidev
dc38f8c041 Bugfix/fix nyoo indexer (#1547)
* Line endings...

* Adjusting selectors
2017-07-12 06:34:14 +02:00
chibidev
17f544be36 Bugfix/1532 1539 fix manual search circular reference (#1543)
* Line endings...

* Encoding Encoding in a reasonable way

Sadly Encoding contains a self-reference somewhere, which makes it
really hard for the json serializer to automatically encode it...
Probably there's no value in sending it over especially since no one is
using it, however just for the sake of the argument, let's just
serialize it in a reasonable way. Maybe someday there will be someone
expecting this. Or we clearly separate DTOs and models...

- Fixes Jackett/Jackett#1532
- Fixes Jackett/Jackett#1539

* Seriously... Please port this to dotnet Core so that I can use something other than @drunkvs on my MacBook

* Fix autofac registration after merge

* "Implement" new function of interface in test project
2017-07-11 22:32:56 +02:00
kaso17
fb59e84def Fix autofac exception 2017-07-11 22:17:56 +02:00
Francis Noel
de8e33e647 Add mania-Torrent tracker (#1534)
* Add mania-Torrent tracker* Update README
2017-07-11 22:00:29 +02:00
kaso17
cf1bbc603e Update BJShare.cs 2017-07-11 21:58:15 +02:00
Guizzoni
ecc60e59d8 Update BJShare.cs 2017-07-11 21:58:15 +02:00
Guizzoni
e7be6faf2f Update BJShare.cs
Updated so it properly replaces "HD" to "720p" since this is the tracker's default naming pattern for every 720p release.
2017-07-11 21:58:15 +02:00
chibidev
9e3076dde6 I have a feeling I'm breaking the world and bringing the apocalypse
It's quite hard to encapsulate something this large. This refactor
contains multiple attacks on the current architecture and is changing
things that were probably created quite a while back then. Luckily this
was done in increments so it mustn't be that impossible to recall what
has been done. I just need to relax my memory a little bit.

So the basic idea was quite simple. Let's distingush metas and normal
indexers a little bit more. Both of them were originating from
BaseIndexer, however very little of the functionality was actually
shared between them. Actually quite a few things made it even harder to
implement a different kind of indexer, especially for a newcomer for
both Jackett and C#.

Then in order to further reduce whatever was encapsulated in
any kind of, a couple things had to be changed. Like CardigannIndexer,
which probably had quite a mindshift change. IndexerManager and the
configuration management were also encapsulated and refactored, and now
I have a feeling that although the code could be improved, at least the
responsibilities of services and what they actually do is now clearer.

Anyhow, it would be safe to assume that I will not be able to go
step-by-step and define everything that has been changed. I'm sorry.
2017-07-11 21:53:46 +02:00
Khogniak
35103206cf Replace T411 (closed) with YggTorrent (new replacement) (#1536)
* Replaced old T411 with new YggTorrent

* Updated Readme

* Fix testing. Added dynamic path
2017-07-09 18:24:00 +02:00
kaso17
29cf00560f Merge branch 'master' of https://github.com/Jackett/Jackett.git 2017-07-08 15:23:40 +02:00
kaso17
9ef9302808 MoreThanTV: fix complete season searching 2017-07-08 15:23:36 +02:00
gprime44
616b436648 Add NextTorrent tracket (#1523)
* Create nexttorrent.yml

init

* Finish definition for next torrent

* Add nexttorrent tracker

* Update README.md
2017-07-08 14:01:21 +02:00
Francis Noel
702e975d57 Add cpasbien tracker (#1527)
* Add cpasbien tracker

* revert project inclusion

* Update README.md
2017-07-08 13:59:34 +02:00
kaso17
6c90016c0c RevolutionTT: ignore donation item 2017-07-08 13:54:37 +02:00
kaso17
90152a7eed The New Retro: fix login 2017-07-06 21:17:50 +02:00
kaso17
dabd95655b Torrent-Syndikat: add auto relogin 2017-07-06 21:16:53 +02:00
kaso17
4f938e3ea8 Add Zamunda.net/Zelka.org 2017-07-06 20:37:29 +02:00
kaso17
f21a721ddb FileList: add auto relogin 2017-07-06 20:31:07 +02:00
kaso17
7d65e60750 Add Zelka.org tracker
Thank you @eybox
2017-07-06 20:21:06 +02:00
kaso17
c87f1b3949 Add Zamunda.net tracker
Thank you @eybox
2017-07-06 20:20:39 +02:00
Luis Almanzar
08b471fd1e Fix title for elements with comments. (#1520)
* Fix title for elements with comments.

* Update nyaasi.yml
2017-07-06 19:38:38 +02:00
chibidev
4a0d2dcc57 Feature/metaindexer torrent download improvement (#1517)
* Line endings...

* Improve download handling of torrents in metas

Until now, downloads were handled by metas, however they had to "guess"
which indexer the result was originating from and resolve to that
indexer. While this has been working without an issue, it really
shouldn't be considered stable.

Therefore indexers now link themselves to the results they provide. In
order to keep my sanity and automate this as much as possible, I had to
slightly modify the interface (sorry, everyone).
2017-07-03 07:15:47 +02:00
chibidev
91eae526f9 Bugfix/1404 fix potato search (#1516)
* Line endings...

* Integrating OMDB into Potato results

Now PotatoController will actually produce results

* VS complains it could be simplified, yet AppVeyor cannot do anything with it...

* How come only Appveyor unveils this error? @drunkvs
2017-07-01 18:23:57 +02:00
chibidev
11fd2db5a5 Bugfix/cannot add cardigann indexers (#1511)
* Line endings...

* Fix invalid ID for cardigann indexers (refactoring regression - my bad)
2017-06-29 20:46:36 +02:00
chibidev
75e7ce81c2 Feature/omdb api integration (#1509)
* Line endings...

* Refactoring how MetaIndexers handle fallbacks

Originally this modification was part of a much larger refactoring,
however for the sake of reviewability I split it into smaller chunks.
Sadly it is still quite large.
I wanted to split it even more, however after a certain point there was
really no value in creating smaller chunks. The biggest part of this
modification would be still huge.

So all in all, there're 3 aspects of this modification
- It modifies BaseIndexer so that it now implements IIndexer (will be
very useful later on)
- Resolving most of the warnings currently in Jackett (the only ones
remaining are related to Autofac, however if I could I would just burn
Autofac altogether rather than fix the warnings. Will open discussion on
this.)
- Biggest part: refactoring how MetaIndexers handle fallbacks and how
they provide the final result set

MetaIndexers now accept any kind of fallback and filtering mechanism
that implements the necessary interface, so that in the future IMDB
fallback and filtering won't be the only one. I know there are not a lot
of unit tests around Jackett at the moment, however this renders the
class much more unittestable as well.

* Integrate OMDB API for the fallback option

* Safeguarding when no API key is specified

* Autofac started complaining... I don't understand...

* How did that not make the previous commit?
2017-06-29 07:53:25 +02:00
chibidev
345602926e Feature/cleaning up aggregate indexer fallback (#1507)
* Line endings...

* Refactoring how MetaIndexers handle fallbacks

Originally this modification was part of a much larger refactoring,
however for the sake of reviewability I split it into smaller chunks.
Sadly it is still quite large.
I wanted to split it even more, however after a certain point there was
really no value in creating smaller chunks. The biggest part of this
modification would be still huge.

So all in all, there're 3 aspects of this modification
- It modifies BaseIndexer so that it now implements IIndexer (will be
very useful later on)
- Resolving most of the warnings currently in Jackett (the only ones
remaining are related to Autofac, however if I could I would just burn
Autofac altogether rather than fix the warnings. Will open discussion on
this.)
- Biggest part: refactoring how MetaIndexers handle fallbacks and how
they provide the final result set

MetaIndexers now accept any kind of fallback and filtering mechanism
that implements the necessary interface, so that in the future IMDB
fallback and filtering won't be the only one. I know there are not a lot
of unit tests around Jackett at the moment, however this renders the
class much more unittestable as well.

* Autofac started complaining... I don't understand...
2017-06-28 07:31:38 +02:00
Rob Nadin
780ea4c631 KickAssTorrent: Fix date parsing (#1502) 2017-06-26 20:08:11 +02:00
chibidev
cf9d87a7d8 Bugfix/1471 aggregate exception when fallback (#1504)
* Line endings...

* Fixes an issue when the aggregate doesn't require fallback

Apparently operating on null with LINQ is not safe :)
- This fixes Jackett/Jackett#1471
2017-06-26 19:46:14 +02:00
kaso17
3f2d6f0cee Add support for non numeric episodes 2017-06-25 18:25:16 +02:00
kaso17
22cf450d07 TorrentDay: fix login 2017-06-25 18:15:12 +02:00
kaso17
8bd7233756 Rarbg: add support for unknown IMDB IDs 2017-06-25 17:37:33 +02:00
kaso17
2807a71e0e DataScene: update to new layout 2017-06-25 17:07:53 +02:00
kaso17
b60bcda109 Rockhard Lossless: update to new layout 2017-06-25 16:56:31 +02:00
kaso17
4d36165cdf Xtreme Zone: update to new layout 2017-06-25 16:42:17 +02:00
hed0nist
44de6e5459 fix incorrect naming rules (#1500)
put the date in the right place
2017-06-25 16:28:31 +02:00
D4rk56
d31c593d4b Domain change .top -> .cc (#1495) 2017-06-23 19:56:21 +02:00
David Dobmeier
0473029277 Resolved null pointer causing searches to fail on RevolutionTT (#1494) 2017-06-23 19:56:03 +02:00
adamwinn
3bef19cbfe Add Blutopia (#1489) 2017-06-23 19:54:09 +02:00
CW2aNmYM0ryJ
79fc4850ed NCore: Add 2factor support (#1465) 2017-06-13 14:00:41 +02:00
D4rk56
7cf24e906a Torrent9: Domain change .biz -> .top (#1462) 2017-06-08 20:49:02 +02:00
kaso17
9870d38cbb Il Corsaro Nero: workaround incomplete CA chain 2017-06-08 11:07:09 +02:00
kaso17
994604271b Merge branch 'master' of https://github.com/Jackett/Jackett.git 2017-06-08 10:34:59 +02:00
kaso17
81f2c7e91c Bit HDTV: adjust code to updated HTML 2017-06-08 10:34:53 +02:00
Mohammed Sohail
b0788e491e [Fix] Redirection errors (#1461)
- The Geeks uses SSL.
2017-06-08 10:32:21 +02:00
187 changed files with 7001 additions and 4884 deletions

View File

@@ -2,7 +2,7 @@
[![GitHub issues](https://img.shields.io/github/issues/Jackett/Jackett.svg?maxAge=60&style=flat-square)](https://github.com/Jackett/Jackett/issues)
[![GitHub pull requests](https://img.shields.io/github/issues-pr/Jackett/Jackett.svg?maxAge=60&style=flat-square)](https://github.com/Jackett/Jackett/pulls)
[![Build status](https://ci.appveyor.com/api/projects/status/gaybh5mvyx418nsp?svg=true)](https://ci.appveyor.com/project/camjac251/jackett)
[![Build status](https://ci.appveyor.com/api/projects/status/gaybh5mvyx418nsp/branch/master?svg=true)](https://ci.appveyor.com/project/camjac251/jackett)
[![Github Releases](https://img.shields.io/github/downloads/Jackett/Jackett/total.svg?maxAge=60&style=flat-square)](https://github.com/Jackett/Jackett/releases/latest)
[![Docker Pulls](https://img.shields.io/docker/pulls/linuxserver/jackett.svg?maxAge=60&style=flat-square)](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

View File

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

View File

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

View File

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

View File

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

View File

@@ -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">&times;</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>

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

View File

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

View File

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

View File

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

View File

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

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

View File

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

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

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

View File

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

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

View 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"

View 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"

View 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"

View File

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

View File

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

View File

@@ -6,6 +6,8 @@
encoding: UTF-8
links:
- https://ilcorsaronero.info/
certificates:
- FFB230323B987B07C24DCC13BC892CF30536F015 # incomplete CA chain
caps:
categorymappings:

View File

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

View 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&amp;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"

View File

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

View 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"

View File

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

View File

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

View File

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

View File

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

View 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"

View File

@@ -6,7 +6,7 @@
type: private
encoding: UTF-8
links:
- http://thegeeks.click/
- https://thegeeks.click/
caps:
categorymappings:

View File

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

View File

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

View 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"

View File

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

View File

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

View 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"

View 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"

View 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"

View 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"

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

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

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

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

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

View File

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

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

View File

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

View File

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

View File

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