Compare commits

...

179 Commits

Author SHA1 Message Date
flightlevel
121736358b Revert "Pretome: Remove" (#446) 2016-08-18 20:55:58 +10:00
flightlevel
1b0ea00c88 TTN: Update Login (#445)
TTN: Update Login
2016-08-18 17:29:56 +10:00
flightlevel
4be1c19c50 Enter to search (#444)
Enter to search
2016-08-18 17:23:43 +10:00
flightlevel
aa55849e62 Pretome: Remove (#443)
Pretome: Remove
2016-08-18 17:09:16 +10:00
flightlevel
1d2093fc32 Bluetigers: Add category (#442)
Bluetigers: Add category
2016-08-18 16:15:50 +10:00
flightlevel
5a5d83e5c1 Remove Phxbit (#441)
Remove Phxbit
2016-08-18 16:05:58 +10:00
flightlevel
155632e85d IPT: Update Parsing (#440)
IPT: Update Parsing
2016-08-18 16:05:32 +10:00
Joel Gillman
cac185f747 Update README.md for Linux/OSX installation (#424) 2016-08-05 21:58:56 +10:00
Thomas Gillen
6d0d502bd3 Add music categories to AnimeBytes indexer (#418) 2016-08-05 21:58:29 +10:00
Superpiffer
ccde6fb53b TorrentLeech: Fix login (#412) (#414)
* Fix login #412

* Update TorrentLeech.cs
2016-08-05 21:57:43 +10:00
flightlevel
2afce9f2d9 BeyondHD; Fix Recaptcha (#409)
BeyondHD; Fix Recaptcha
2016-07-27 14:50:53 +10:00
flightlevel
2647457706 SpeedCd: Update URL (#408)
SpeedCd: Update URL
2016-07-27 14:14:57 +10:00
kobik
ac5e69a3b2 New indexer - Hebits.net (#406)
* Added Hebits.net as an indexer - a private Israel tracker

* Added new indexer Hebits to the readme file

* Deleted debug messages leftovers
2016-07-27 14:08:06 +10:00
d2dyno
45fb2a27c3 Updated BeyondHD URL (#390)
Updated BeyondHD to new URL.
2016-07-04 21:58:33 +10:00
Umur Kontacı
fb3d7ae81b Fix builds on case-sensitive filesystems (#377) 2016-06-26 20:32:48 +10:00
Codehhh
9b62e8af71 Add PTP as a new Indexer (#373)
* Added PassThePopcorn Indexer

* Fixed PTP Indexer
2016-06-23 20:34:19 +10:00
flightlevel
3f292b5e47 Freshon: Fix Urls (#374)
Freshon: Fix Urls
2016-06-23 20:28:08 +10:00
Chris Mattera
bd2abddb09 Modified to use new download URL format (#369) 2016-06-21 19:28:55 +10:00
Joel Gillman
5135748d1d Update "issues page" link in README (#358)
The "issues page" link in the Contributing section was pointing to the old repo! I just updated.
2016-06-08 08:53:38 +02:00
flightlevel
0fc3d224ab Allow Custom Data Folder (#355)
Allow Custom Data Folder
2016-05-28 19:40:55 +10:00
flightlevel
ac07cc34cd SpeedCD: Fix Login (#354)
SpeedCD: Fix Login
2016-05-28 19:40:39 +10:00
flightlevel
3730e05f20 TorrentDay: Add Audio (#353)
TorrentDay: Add Audio
2016-05-28 19:40:16 +10:00
flightlevel
2644fd813e Bluetigers fix SSL issue (#346) 2016-05-18 20:33:37 +10:00
flightlevel
ece16d1075 TransmitheNet: Fix Titles with extension (#343) 2016-05-17 23:25:26 +10:00
Fredrik Löwenhamn
3b13fa84a4 Fixed saving alphaRatio json (#342) 2016-05-17 23:04:15 +10:00
flightlevel
cda5ea3207 Update README.md 2016-05-14 22:44:49 +10:00
flightlevel
0746616b43 Revert "SSL Fix by default, Now use TLS (1.2, 1.1, 1) by default" (#339) 2016-05-14 22:42:16 +10:00
JigSaw
28199ab4be SSL Fix by default, Added support of TLS 1.1 & 1.2 (#337)
* SSL Fix by default, Now use TLS (1.2, 1.1, 1) by default
* Workaround to use TLS 1.2 & 1.1 on Mono < 4.3
2016-05-14 00:46:56 +02:00
JigSaw
b29c578adb Fixed FADN Provider (#336)
New Search Engine Template
2016-05-13 23:41:10 +02:00
smarshallsay
b42f2a0972 Deal with carriage return in date string (#325) 2016-04-30 21:48:52 +10:00
hex
040deb2bfb Added SceneFZ tracker (#319)
* Added SceneFZ tracker
* SceneFZ tracker new logo size and updated README
2016-04-26 12:40:54 +02:00
flightlevel
ef8b4e685e Update README.md 2016-04-20 20:14:55 +10:00
flightlevel
364860199c Merge pull request #316 from flightlevel/ttn_url2
TTN: Update search string
2016-04-20 20:11:55 +10:00
flightlevel
3f2a6fd3f7 Merge pull request #313 from coolius/myanonamouse
Added MyAnonamouse tracker
2016-04-19 21:37:15 +10:00
flightlevel
2671cf00e0 Merge pull request #312 from coolius/ipt_alternatelink
Added AlternateLink to IPTorrents
2016-04-19 20:38:20 +10:00
coolius
6a6941d01c Copy myanonamouse logo to output directory 2016-04-18 15:22:50 +01:00
coolius
5b3862bc3d Added MyAnonamouse tracker 2016-04-18 15:02:15 +01:00
coolius
0452f5ad06 Added AlternateLink to IPTorrents 2016-04-18 14:42:53 +01:00
flightlevel
264fc995b2 Merge pull request #307 from flightlevel/ttn_url
TTN: Update search url
2016-04-13 21:51:34 +10:00
flightlevel
acc75acb9c TTN: Update search url
TTN: Update search url
2016-04-13 21:33:14 +10:00
JigSaw
a5b1332f95 Optimized & Fixed FADN Provider
LoginCheck, New Logo, Optimized
2016-04-05 14:52:35 +02:00
JigSaw
8871a631b1 Fix Manga Anime Category for PhxBit Provider
Wrong category fixed
2016-04-03 20:55:37 +02:00
JigSaw
bfb58f53f5 Xthor Provider
French Private Tracker
2016-04-03 20:31:44 +02:00
JigSaw
d14717c88e Merge pull request #294 from JigSawFr/providers/phxbit-fix
Download FIX for PhxBit
2016-04-03 18:57:23 +02:00
JigSawFr
d8ff110d8b DownloadBase & DownloadUrl FIX for PHX 2016-04-03 18:46:46 +02:00
flightlevel
23737d3b19 Add Fuzer to readme 2016-04-01 22:39:37 +11:00
flightlevel
b11516760b Merge pull request #288 from OneBigGuy/master
Add Fuzer.me tracker
2016-04-01 22:38:47 +11:00
JigSaw
3a7f8ce268 Merge pull request #291 from JigSawFr/readme-fix
Cleanup README
2016-03-30 09:37:57 +02:00
JigSawFr
7a5b2e5c6a Cleanup README 2016-03-30 09:36:59 +02:00
OneBigGuy
ead129eda9 Add Fuzer.me tracker 2016-03-30 10:10:46 +03:00
JigSaw
b7cd0aeca8 Merge pull request #290 from Jackett/providers/phxbit
PhxBit Provider
2016-03-30 00:14:56 +02:00
JigSawFr
708b45b02f PHXBIT Provider 2016-03-29 23:54:19 +02:00
OneBigGuy
75f4342499 Merge https://github.com/Jackett/Jackett 2016-03-27 16:51:15 +03:00
OneBigGuy
24ad51ad15 Add Fuzer.me tracker 2016-03-27 16:50:02 +03:00
flightlevel
ecc3dd26db Merge pull request #284 from Jackett/revert-273-freshon--refactor
Revert "Freshon: Use AngleSharp for parsing"
2016-03-25 10:29:05 +11:00
flightlevel
aecee29219 Revert "Freshon: Use AngleSharp for parsing" 2016-03-25 10:16:35 +11:00
flightlevel
bda73dc9c1 Merge pull request #279 from flightlevel/tehconnection
TehConnection: Fix IMDB ID searches
2016-03-20 20:45:59 +11:00
flightlevel
63d2407e4f TehConnection: Fix IMDB ID searches
TehConnection: Fix IMDB ID searches
2016-03-20 20:40:54 +11:00
flightlevel
baf44314e9 Merge pull request #278 from flightlevel/transmithenet
Add Transmithe.Net tracker
2016-03-19 22:43:21 +11:00
flightlevel
29ef28b6d7 Add Transmithe.Net tracker
Add Transmithe.Net tracker
2016-03-19 22:40:00 +11:00
flightlevel
86dad52919 Merge pull request #277 from flightlevel/imdbid--match
Fix IMDB Matching
2016-03-19 22:32:56 +11:00
flightlevel
6ff05656ef Fix IMDB Matching
Comparing number to string was failing when imdb id had a leading zero
2016-03-19 19:13:06 +11:00
flightlevel
71c195cafb Merge pull request #273 from flightlevel/freshon--refactor
Freshon: Use AngleSharp for parsing
2016-03-17 20:56:29 +11:00
flightlevel
dda0ae2485 Freshon: Use AngleSharp for parsing
Freshon: Use AngleSharp for parsing
2016-03-17 20:52:29 +11:00
flightlevel
69dc63c726 Merge pull request #272 from flightlevel/anglesharp-update
Update Anglesharp
2016-03-17 20:51:02 +11:00
flightlevel
ee65721da1 Update Anglesharp
Update Anglesharp
2016-03-17 20:40:17 +11:00
Azerelat
8ffb91f414 Merge pull request #264 from lowet84/master
Updated encryption service for better use with Docker
2016-03-13 10:46:53 +00:00
Fredrik Löwenhamn
50d931b4fb Updated encryptioon service for better use with Docker 2016-03-04 10:05:47 +01:00
flightlevel
6f475b18f3 Merge pull request #259 from flightlevel/ipturl
Fix IPTorrents url encoding
2016-02-27 21:13:30 +11:00
flightlevel
782211d06a Fix IPTorrents url encoding
Fix IPTorrents url encoding
Issue https://github.com/Jackett/Jackett/issues/256
2016-02-27 21:09:19 +11:00
Azerelat
4f5d7a3d54 Make category mapping a little less confusing #255 2016-02-24 18:40:16 +00:00
flightlevel
f26f2d6f25 Merge pull request #249 from flightlevel/tehconnection
TehConnection: Attempt to fix cookie expiration
2016-02-17 23:36:04 +11:00
flightlevel
6ccbfd6443 TehConnection: Attempt to fix cookie expiration
TehConnection: Attempt to fix cookie expiration
2016-02-17 23:30:04 +11:00
flightlevel
c896ed8238 Merge pull request #248 from flightlevel/danishbits
DanishBits: Fix publish time
2016-02-17 22:17:00 +11:00
flightlevel
1879ed89df DanishBits: Fix publish time
DanishBits: Fix publish time
2016-02-17 22:10:36 +11:00
flightlevel
11f99a44d3 Merge pull request #247 from flightlevel/ilovetorrents
ILoveTorrents: Add to readme
2016-02-16 19:42:08 +11:00
flightlevel
f8fcf2fb79 ILoveTorrents: Add to readme
ILoveTorrents: Add to readme
2016-02-16 19:32:18 +11:00
flightlevel
c36a3f558a Merge pull request #246 from lowet84/master
Fixed a bug in the download link.
2016-02-16 19:22:10 +11:00
Fredrik Löwenhamn
07a88919b4 Fixed a bug in the download link.
Added more categories.
2016-02-16 08:02:12 +01:00
Azerelat
d02cb3fefc Merge pull request #245 from lowet84/master
Added support for ILoveTorrents.me
2016-02-15 20:22:45 +00:00
Fredrik Löwenhamn
f05eca3a9f Added support for ILoveTorrents.me 2016-02-15 13:10:06 +01:00
flightlevel
ccc2441a55 Merge pull request #243 from flightlevel/scenetime
SceneTime: Add category mapping and fix search
2016-02-14 15:19:29 +11:00
flightlevel
5aaa402287 SceneTime: Add category mapping and fix search
SceneTime: Add category mapping and fix search
2016-02-14 15:15:09 +11:00
flightlevel
97849dfcaf Merge pull request #242 from flightlevel/scenetime
SceneTime: Fix Column parsing
2016-02-13 17:01:23 +11:00
flightlevel
f2a899eea3 SceneTime: Fix Column parsing
SceneTime: Column numbers seem to be related to user settings, use
column names instead. Add category mapping support
2016-02-13 16:55:23 +11:00
flightlevel
80686c81ee Merge pull request #236 from flightlevel/bitmetvinfo
BitMeTv: Add instructions
2016-02-07 16:49:42 +11:00
flightlevel
189483b2b7 BitMeTv: Add instructions
BitMeTv: Add instructions to turn on SSL
2016-02-07 16:39:20 +11:00
flightlevel
e7cc147121 Merge pull request #235 from flightlevel/scenetimefix
Scenetime: Fix parsing
2016-02-07 16:38:18 +11:00
flightlevel
73f044c0f2 Scenetime: Fix parsing
Scenetime: Fix parsing as per
https://github.com/Jackett/Jackett/issues/231#issuecomment-180289081
2016-02-07 16:22:33 +11:00
flightlevel
a268893475 Merge pull request #227 from Jackett/revert-226-freshonrefactor
Revert "Freshon: Changed parser to AngleSharp"
2016-01-29 23:56:03 +11:00
flightlevel
c20e4d0dfa Revert "Freshon: Changed parser to AngleSharp" 2016-01-29 23:50:23 +11:00
flightlevel
73536b11e2 Merge pull request #226 from flightlevel/freshonrefactor
Freshon: Changed parser to AngleSharp
2016-01-29 23:38:11 +11:00
flightlevel
a35695358d Merge pull request #225 from flightlevel/BasicIndexerMessage
Allow instructions on the basic indexer
2016-01-29 23:31:47 +11:00
flightlevel
7312c8c230 Freshon: Changed parser to AngleSharp
Freshon: Changed parser to AngleSharp
2016-01-29 23:30:12 +11:00
flightlevel
04fea52956 Allow instructions on the basic indexer
Allow instructions on the basic indexer
2016-01-29 23:28:14 +11:00
flightlevel
cd642a48b6 Merge pull request #224 from flightlevel/AddAnglesharp
Add AngleSharp package
2016-01-29 21:36:49 +11:00
flightlevel
89e0498224 Add AngleSharp package 2016-01-29 21:31:23 +11:00
flightlevel
35f9c05cb9 Merge pull request #223 from flightlevel/urlencodetorznabresponse
URL Encode File Name
2016-01-29 21:19:42 +11:00
flightlevel
8a54f9d825 URL Encode File Name
URL Encode file names in proxy link
Issue https://github.com/Jackett/Jackett/issues/222
2016-01-29 21:15:30 +11:00
Azerelat
34caa78de1 Merge pull request #218 from some-guy-23/patch-1
Switched favicon to relative URL
2016-01-28 17:57:27 +00:00
some-guy-23
e28fadbd68 Merge pull request #2 from some-guy-23/patch-2
Switched favicon to relative URL
2016-01-27 14:23:41 -05:00
some-guy-23
0930048d8b Switched favicon to relative URL 2016-01-27 14:22:12 -05:00
some-guy-23
42495e36e5 Switched favicon to relative URL 2016-01-27 14:21:32 -05:00
flightlevel
4f36d19b00 Merge pull request #201 from flightlevel/speedcd
SpeedCd: Fix Query String
2016-01-20 19:50:21 +11:00
flightlevel
2f682ca53f SpeedCd: Fix Query String
SpeedCd: Fix Query String
2016-01-20 19:45:39 +11:00
flightlevel
aa7a33496c Merge pull request #199 from flightlevel/torrentbytesfix
TorrentBytes: Allow for missing category and fix time
2016-01-20 00:29:20 +11:00
flightlevel
1f94dec089 Merge pull request #198 from flightlevel/speedcd
SpeedCd: Change API to parsing
2016-01-20 00:28:38 +11:00
flightlevel
e57cbe3b44 TorrentBytes: Allow for missing category and fix time
TorrentBytes: Allow for missing category and fix time
2016-01-20 00:20:19 +11:00
flightlevel
7b74f981e3 SpeedCd: Change API to parsing
SpeedCD API no longer working, changed to parsing
2016-01-20 00:08:00 +11:00
Azerelat
2901cceea9 Fix cache items not expiring and change redirect to a temp one as we can now change the base path 2016-01-17 13:19:18 +00:00
Azerelat
ea4d0fe701 AnimeBytes Sonarr Fix (Will only fix season 1 but we don't have season info). 2016-01-16 22:36:56 +00:00
Azerelat
02a57533f9 Merge pull request #193 from raspdealer/patch-4
Change below to above inside steps
2016-01-16 21:07:24 +00:00
Azerelat
99f0e9c9f6 Merge pull request #192 from raspdealer/patch-3
Enable SSL on Bluetigers
2016-01-16 21:07:13 +00:00
Raspdealer
d1225c17b2 Change below to above inside steps 2016-01-16 11:47:01 +01:00
Raspdealer
2adecae9bf Enable SSL on Bluetigers 2016-01-16 11:41:51 +01:00
Azerelat
c7aa0d7b0f Fix better reverse proxy support 2016-01-14 20:15:10 +00:00
Azerelat
a69ecf3e06 Merge pull request #179 from tehjojo/master
Better reverse proxy support
2016-01-14 19:29:57 +00:00
Azerelat
9414e7a4b8 Merge pull request #188 from JigSawFr/providers/fnt-remove
Removed FNT provider
2016-01-14 19:20:25 +00:00
Azerelat
d9473781d3 Merge pull request #189 from JigSawFr/providers/bluetigers-ssl-removed
Fixed Disabled SSL on tracker side with BlueTigers Provider
2016-01-14 19:19:57 +00:00
JigSawFr
4952199e68 Fixed Disabled SSL on tracker side with BlueTigers Provider 2016-01-14 16:55:09 +01:00
JigSawFr
388460993d Removed FNT provider 2016-01-14 14:57:00 +01:00
flightlevel
f21c1f0400 Merge pull request #183 from flightlevel/FixNcoreFileNameCase
Fix NCore File Name Casing
2016-01-13 22:56:11 +11:00
flightlevel
4db62bbf63 Fix NCore File Name Casing 2016-01-12 22:49:01 +11:00
Michael Robinson
42ec634cd3 Better reverse proxy support
Added "base path override" config option that makes all links and
redirects work with your reverse proxy.
Fixed post config update reload to work properly.
Make redirects and ajax calls use relative pathing.
2016-01-10 19:59:40 -07:00
Azerelat
a07de4d1e9 Merge pull request #175 from twistedroutes/master
demonoid to support torrentPotato
2016-01-10 20:12:18 +00:00
Azerelat
0361a88856 Merge branch 'master' of https://github.com/Jackett/Jackett.git 2016-01-10 20:06:12 +00:00
Azerelat
a88f248b75 Add logging to updater 2016-01-10 20:06:09 +00:00
twistedpear
82f06d0b46 Added movies category to demonoid - simple version, no subcats - all qualities 2016-01-09 15:11:22 -05:00
Azerelat
10e019a1dd Merge pull request #174 from JigSawFr/providers/abn
Abnormal Provider
2016-01-09 18:46:38 +00:00
Azerelat
173e26c054 SCC queries not uri encoded #170 2016-01-09 18:45:02 +00:00
JigSawFr
24f8d26b2d Updated README 2016-01-09 03:08:39 +01:00
JigSawFr
8ceb8ebbe7 ABN Provider 2016-01-09 03:06:21 +01:00
Azerelat
71c583d359 AnimeBytes Fix stripping of episode number from search term 2016-01-07 00:12:56 +00:00
Azerelat
9d4e8f4bda Merge pull request #160 from twistedroutes/bitsoup
Put GUID back into release
2016-01-06 23:02:34 +00:00
twistedpear
65d2a88591 Put GUID back into release 2016-01-06 16:56:46 -05:00
Azerelat
a04d296b55 Make Un-configured list smaller 2016-01-06 19:53:13 +00:00
Azerelat
79452053ee Merge pull request #153 from JigSawFr/providers/fadn
French-ADN Provider
2016-01-06 18:37:48 +00:00
Azerelat
7ad5fac9c0 Merge pull request #156 from JigSawFr/providers/fnt-hotfix-dl
FNT Provider ~ Download HOTFIX & SSL
2016-01-06 18:36:35 +00:00
JigSawFr
2456a42608 SSL enabled 2016-01-05 20:26:47 +01:00
JigSawFr
3c396ba880 Fix Torrent Files Downloading 2016-01-05 20:25:00 +01:00
JigSawFr
65e4d1e068 Rebase on upstream 2016-01-05 15:41:54 +01:00
JigSawFr
e643134f09 Updated README 2016-01-05 15:33:13 +01:00
JigSawFr
19bc3c7b36 FADN Provider 2016-01-05 15:30:50 +01:00
Azerelat
a50e31ec3f Merge pull request #151 from tehjojo/master
Changed resource paths to be relative
2016-01-04 20:51:51 +00:00
Azerelat
ff96389864 Merge pull request #149 from JigSawFr/providers/fnt
FNT Provider
2016-01-04 20:50:42 +00:00
Azerelat
efd5a8cc98 Merge pull request #150 from JigSawFr/providers/wihd
WiHD Provider ~ Improved
2016-01-04 20:41:15 +00:00
Michael Robinson
d5c59109b1 Changed resource paths to be relative 2016-01-03 20:14:44 -07:00
JigSawFr
4a9fcdbc9b Fixed some typo & removed unecessary comment 2016-01-04 00:47:42 +01:00
JigSawFr
6bea0b009a Upgraded with new functions and improvements,
Empty search return now from all (fixed term before),
Fix Browser XHR request not removed if no result,
Fix some typo
2016-01-04 00:09:32 +01:00
JigSawFr
ff86a15ddd Rebase on upstream 2016-01-03 22:15:04 +01:00
Azerelat
d204ce6f39 Fix TVChaosUK and missing logos 2016-01-03 20:40:59 +00:00
JigSawFr
6bc011f678 FNT Provider 2016-01-03 19:39:53 +01:00
Azerelat
acdf198b05 Merge pull request #137 from JigSawFr/providers/wihd
WiHD Provider
2016-01-03 14:38:18 +00:00
JigSawFr
5f2dfcf499 Updated README 2015-12-29 12:07:34 +01:00
JigSawFr
aa69d1a3b9 Debug logging only if debug level is true 2015-12-29 11:51:52 +01:00
JigSawFr
29bde68337 Cache only used in case of search term 2015-12-29 11:30:55 +01:00
Sébastien Robert
e9ae2d4cd8 WiHD Provider 2015-12-29 11:17:18 +01:00
Azerelat
0ba6188e4b Merge pull request #132 from twistedroutes/master
XSpeeds
2015-12-29 01:31:38 +00:00
twistedpear
283a003aef remember xspeeds cookies if they have changed. This makes future queries much quicker 2015-12-28 12:15:09 -05:00
twistedpear
a50ab9c49b Improved cookie accumulation to reduce roundtrips to the server. 2015-12-28 11:16:53 -05:00
twistedpear
b4e42b4180 Improved the category search - can now use mutli categories 2015-12-28 10:27:02 -05:00
twistedpear
f7efd83e09 Put ProxyConnection back into Startup.cs 2015-12-28 09:53:59 -05:00
twistedpear
f67381d0ac Merge remote-tracking branch 'upstream/master' 2015-12-28 09:46:38 -05:00
garreth.jeremiah@gmail.com
af0c15be2c added notice in log when proxying is used
bitsoup multi category searches look for all cats instead of making multiple queries
2015-12-28 08:42:11 -05:00
Azerelat
4ce6f6b048 Update README.md 2015-12-28 11:50:33 +00:00
Azerelat
828091128e Respect pre-release flag 2015-12-27 22:55:18 +00:00
Azerelat
f8c0b9a80f Merge pull request #133 from some-guy-23/indexer-RevolutionTT
Added new tracker/indexer class for RevolutionTT.me
2015-12-27 21:39:48 +00:00
garreth.jeremiah@gmail.com
f4129dc4a0 Added simple (non-auth) proxy support including processing the second set of headers (server) vs first (proxy). New command line option (-j 127.0.0.1:8888) to set the proxy and port. unfortunatly both -p and -x were already taken
extended refresh header handling to libcurl and safecurl
also some minor tweaks needed to have Curl 'behave' with certain servers by adding accept-language header to Curl
Added ability/template to allow the user to select their own SiteURL (see BitSoup implementation) with a minor update to ConfigurationData class to prevent the "display text" from disappearing on errors.
XSpeeds indexer - updated to handle a case where the returned XML occasionally is null terminated resulting in XML parsing exceptions
BitSoup indexer - added user customizable url selection, including some url validation
BaseIndexer - cleaned up some of my earlier implementation for accumulating cookies.
2015-12-24 22:26:39 -05:00
twistedroutes
f6f27e604a Fixed grabbing the wrong download link. Also updated the comment link 2015-12-22 12:17:49 -05:00
twistedroutes
bc1dd0e9e8 Added Bitsoup tracker. For multi-category searches will perform multiple searches as the tracker search didn't support multiple categories. Recommend to the user to search for "all" instead. Consider changing this approach later. 2015-12-21 22:12:43 -05:00
some-guy-23
6b1dba2dc3 Added RSS feed to RevolutionTT 2015-12-21 20:03:50 -05:00
some-guy-23
e4a47927b9 Fixed RevolutionTT so that login can use username/password (instead of cookie) 2015-12-21 18:18:07 -05:00
some-guy-23
226c27f903 Added new tracker/indexer class for RevolutionTT.me 2015-12-21 16:10:39 -05:00
garreth.jeremiah@gmail.com
5acaf9dbb8 performQuery did not handle the situation where the credentials had timed out or the cloudflare DDoS was re-requested. Added a temporary solution by re-doing the log-in process. It is a little slow, so users of this indexer will see a 20s delay. I will find a way to improve that in a subsequent fix. 2015-12-19 10:40:01 -05:00
garreth.jeremiah@gmail.com
a856aba953 XSpeeds.cs - updated configuration data to give notification to use about the initial delay for handling the CloudFlare DDOS Redirection using a new ConfigurationData class that exploits the seemingly unused DisplayInfo
ConfigurationDataBaseicLoginWithRSSAndDisplay - a ConfigData class that provides a display item member.  I am new to c# so I haven't figured out if I coud have avoided this by added it to the object after instantiation.  Looks possible with expandoobject but I felt that would be changing too much
2015-12-18 13:02:07 -05:00
garreth.jeremiah@gmail.com
ce351dd3aa Corrected typo in response.statusCode to response.StatusCode 2015-12-18 12:08:36 -05:00
garreth.jeremiah@gmail.com
99125386f0 XSpeeds - based on TVCHaosUK. Because f clourflare redirection, needs to hit the site twice. Once for cloudflare DDoS redirection (to obtain all necessary cookies) and second to login. 2015-12-18 12:06:51 -05:00
garreth.jeremiah@gmail.com
44d6ac2f04 Run - added parsing of the http response headers to examine the Refresh header if the response is a 503 (service unavailable). Extract the redirect and redirect delay from the header and follow it. This is to avoid the alternative of trying to calculate the cloudflare challenge/response. 2015-12-18 12:03:38 -05:00
garreth.jeremiah@gmail.com
b5ff430e2d RequestLoginAndFollowRedirect - added "accumulateCookies" which adds cookies from redirection responses. This was needed for xspeeds processing of cloudflare redirection
FollowIfRedirect - during redirects this can keep the cookies
2015-12-18 11:55:53 -05:00
92 changed files with 7696 additions and 1077 deletions

View File

@@ -14,6 +14,7 @@ Developer note: The software implements the [Torznab](https://github.com/Sonarr/
#### Supported Private Trackers
* Abnormal
* AlphaRatio
* AnimeBytes
* Avistaz
@@ -21,24 +22,33 @@ Developer note: The software implements the [Torznab](https://github.com/Sonarr/
* BeyondHD
* BIT-HDTV
* BitMeTV
* BitSoup
* BlueTigers
* BTN
* DanishBits
* Demonoid
* EuTorrents
* FileList
* French-ADN
* Freshon
* Fuzer
* HD-Space
* HD-Torrents
* Hebits
* Hounddawgs
* ILoveTorrents
* Immortalseed
* IPTorrents
* PassThePopcorn
* MoreThanTV
* MyAnonamouse
* NCore
* NextGen
* Pretome
* PrivateHD
* RevolutionTT
* SceneAccess
* SceneFZ
* SceneTime
* Shazbat
* SpeedCD
@@ -47,7 +57,11 @@ Developer note: The software implements the [Torznab](https://github.com/Sonarr/
* TorrentDay
* TorrentLeech
* TorrentShack
* TransmitheNet
* TV Chaos UK
* World-In-HD
* XSpeeds
* Xthor
#### Installation on Windows
@@ -61,7 +75,7 @@ Jackett can also be run from the command line using JackettConsole.exe if you wo
* Debian/Ubunutu: apt-get install libcurl-dev
* Redhat/Fedora: yum install libcurl-devel
* For other distros see the [Curl docs](http://curl.haxx.se/dlwiz/?type=devel).
3. Download and extract the latest ```.tar.gz``` release from the [releases page](https://github.com/Jackett/Jackett/releases) and run Jackett using mono with the command "mono JackettConsole.exe".
3. Download and extract the latest `Jackett.Binaries.Mono.tar.gz` release from the [releases page](https://github.com/Jackett/Jackett/releases) and run Jackett using mono with the command `mono JackettConsole.exe`.
Detailed instructions for [Ubuntu 14.x](http://www.htpcguides.com/install-jackett-on-ubuntu-14-x-for-custom-torrents-in-sonarr/) and [Ubuntu 15.x](http://www.htpcguides.com/install-jackett-ubuntu-15-x-for-custom-torrents-in-sonarr/)
@@ -86,7 +100,7 @@ You can get additional logging with the switches "-t -l". Please post logs if y
Please supply as much information about the problem you are experiencing as possible. Your issue has a much greater chance of being resolved if logs are supplied so that we can see what is going on. Creating an issue with '### isn't working' doesn't help anyone to fix the problem.
### Contributing
All contributions are welcome just send a pull request. Jackett's framework allows our team (and any other volunteering dev) to implement new trackers in an hour or two. If you'd like support for a new tracker but are not a developer then feel free to leave a request on the [issues page](https://github.com/zone117x/Jackett/issues). It is recommended to use Visual studio 2015 when making code changes in this project. We currently only support private trackers.
All contributions are welcome just send a pull request. Jackett's framework allows our team (and any other volunteering dev) to implement new trackers in an hour or two. If you'd like support for a new tracker but are not a developer then feel free to leave a request on the [issues page](https://github.com/Jackett/Jackett/issues). It is recommended to use Visual studio 2015 when making code changes in this project. We currently only support private trackers.
### Screenshots

View File

@@ -27,6 +27,9 @@ namespace Jackett.Console
[Option('c', "UseClient", HelpText = "Override web client selection. [automatic(Default)/libcurl/safecurl/httpclient]")]
public string Client { get; set; }
[Option('j', "ProxyConnection", HelpText = "use proxy - e.g. 127.0.0.1:8888")]
public string ProxyConnection { get; set; }
[Option('s', "Start", HelpText = "Start the Jacket Windows service (Must be admin)")]
public bool StartService { get; set; }
@@ -57,6 +60,9 @@ namespace Jackett.Console
[Option('n', "IgnoreSslErrors", HelpText = "[true/false] Linux Libcurl - Ignores invalid SSL certificates")]
public bool? IgnoreSslErrors { get; set; }
[Option('d', "DataFolder", HelpText = "Specify the location of the data folder (Must be admin on Windows) eg. --DataFolder=\"D:\\Your Data\\Jackett\\\"")]
public string DataFolder { get; set; }
[ParserState]
public IParserState LastParserState { get; set; }
}

View File

@@ -1,220 +1,233 @@
using CommandLine;
using CommandLine.Text;
using Jackett;
using Jackett.Console;
using Jackett.Indexers;
using Jackett.Utils;
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Text;
using System.Text.RegularExpressions;
using System.Threading;
using System.Threading.Tasks;
namespace JackettConsole
{
public class Program
{
static void Main(string[] args)
{
try
{
var options = new ConsoleOptions();
if (!Parser.Default.ParseArguments(args, options) || options.ShowHelp == true)
{
if (options.LastParserState != null && options.LastParserState.Errors.Count > 0)
{
var help = new HelpText();
var errors = help.RenderParsingErrorsText(options, 2); // indent with two spaces
Console.WriteLine("Jackett v" + Engine.ConfigService.GetVersion());
Console.WriteLine("Switch error: " + errors);
Console.WriteLine("See --help for further details on switches.");
Environment.ExitCode = 1;
return;
}
else
{
var text = HelpText.AutoBuild(options, (HelpText current) => HelpText.DefaultParsingErrorsHandler(options, current));
text.Copyright = " ";
text.Heading = "Jackett v" + Engine.ConfigService.GetVersion() + " options:";
Console.WriteLine(text);
Environment.ExitCode = 1;
return;
}
}
else
{
if (options.ListenPublic && options.ListenPrivate)
{
Console.WriteLine("You can only use listen private OR listen publicly.");
Environment.ExitCode = 1;
return;
}
/* ====== Options ===== */
// SSL Fix
Startup.DoSSLFix = options.SSLFix;
// Use curl
if (options.Client != null)
Startup.ClientOverride = options.Client.ToLowerInvariant();
// Logging
if (options.Logging)
Startup.LogRequests = true;
// Tracing
if (options.Tracing)
Startup.TracingEnabled = true;
// Log after the fact as using the logger will cause the options above to be used
if (options.Logging)
Engine.Logger.Info("Logging enabled.");
if (options.Tracing)
Engine.Logger.Info("Tracing enabled.");
if (options.SSLFix == true)
Engine.Logger.Info("SSL ECC workaround enabled.");
else if (options.SSLFix == false)
Engine.Logger.Info("SSL ECC workaround has been disabled.");
// Ignore SSL errors on Curl
Startup.IgnoreSslErrors = options.IgnoreSslErrors;
if (options.IgnoreSslErrors == true)
{
Engine.Logger.Info("Curl will ignore SSL certificate errors.");
}
/* ====== Actions ===== */
// Install service
if (options.Install)
{
Engine.ServiceConfig.Install();
return;
}
// Uninstall service
if (options.Uninstall)
{
Engine.Server.ReserveUrls(doInstall: false);
Engine.ServiceConfig.Uninstall();
return;
}
// Reserve urls
if (options.ReserveUrls)
{
Engine.Server.ReserveUrls(doInstall: true);
return;
}
// Start Service
if (options.StartService)
{
if (!Engine.ServiceConfig.ServiceRunning())
{
Engine.ServiceConfig.Start();
}
return;
}
// Stop Service
if (options.StopService)
{
if (Engine.ServiceConfig.ServiceRunning())
{
Engine.ServiceConfig.Stop();
}
return;
}
// Migrate settings
if (options.MigrateSettings)
{
Engine.ConfigService.PerformMigration();
return;
}
// Show Version
if (options.ShowVersion)
{
Console.WriteLine("Jackett v" + Engine.ConfigService.GetVersion());
return;
}
/* ====== Overrides ===== */
// Override listen public
if (options.ListenPublic || options.ListenPrivate)
{
if (Engine.Server.Config.AllowExternal != options.ListenPublic)
{
Engine.Logger.Info("Overriding external access to " + options.ListenPublic);
Engine.Server.Config.AllowExternal = options.ListenPublic;
if (System.Environment.OSVersion.Platform != PlatformID.Unix)
{
if (ServerUtil.IsUserAdministrator())
{
Engine.Server.ReserveUrls(doInstall: true);
}
else
{
Engine.Logger.Error("Unable to switch to public listening without admin rights.");
Environment.ExitCode = 1;
return;
}
}
Engine.Server.SaveConfig();
}
}
// Override port
if (options.Port != 0)
{
if (Engine.Server.Config.Port != options.Port)
{
Engine.Logger.Info("Overriding port to " + options.Port);
Engine.Server.Config.Port = options.Port;
if (System.Environment.OSVersion.Platform != PlatformID.Unix)
{
if (ServerUtil.IsUserAdministrator())
{
Engine.Server.ReserveUrls(doInstall: true);
}
else
{
Engine.Logger.Error("Unable to switch ports when not running as administrator");
Environment.ExitCode = 1;
return;
}
}
Engine.Server.SaveConfig();
}
}
}
Engine.Server.Initalize();
Engine.Server.Start();
Engine.RunTime.Spin();
Engine.Logger.Info("Server thread exit");
}
catch (Exception e)
{
Engine.Logger.Error(e, "Top level exception");
}
}
}
}
using CommandLine;
using CommandLine.Text;
using Jackett;
using Jackett.Console;
using Jackett.Indexers;
using Jackett.Utils;
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Text;
using System.Text.RegularExpressions;
using System.Threading;
using System.Threading.Tasks;
namespace JackettConsole
{
public class Program
{
static void Main(string[] args)
{
try
{
var options = new ConsoleOptions();
if (!Parser.Default.ParseArguments(args, options) || options.ShowHelp == true)
{
if (options.LastParserState != null && options.LastParserState.Errors.Count > 0)
{
var help = new HelpText();
var errors = help.RenderParsingErrorsText(options, 2); // indent with two spaces
Console.WriteLine("Jackett v" + Engine.ConfigService.GetVersion());
Console.WriteLine("Switch error: " + errors);
Console.WriteLine("See --help for further details on switches.");
Environment.ExitCode = 1;
return;
}
else
{
var text = HelpText.AutoBuild(options, (HelpText current) => HelpText.DefaultParsingErrorsHandler(options, current));
text.Copyright = " ";
text.Heading = "Jackett v" + Engine.ConfigService.GetVersion() + " options:";
Console.WriteLine(text);
Environment.ExitCode = 1;
return;
}
}
else
{
if (options.ListenPublic && options.ListenPrivate)
{
Console.WriteLine("You can only use listen private OR listen publicly.");
Environment.ExitCode = 1;
return;
}
/* ====== Options ===== */
// SSL Fix
Startup.DoSSLFix = options.SSLFix;
// Use curl
if (options.Client != null)
Startup.ClientOverride = options.Client.ToLowerInvariant();
// Use Proxy
if (options.ProxyConnection != null)
{
Startup.ProxyConnection = options.ProxyConnection.ToLowerInvariant();
Engine.Logger.Info("Proxy enabled. " + Startup.ProxyConnection);
}
// Logging
if (options.Logging)
Startup.LogRequests = true;
// Tracing
if (options.Tracing)
Startup.TracingEnabled = true;
// Log after the fact as using the logger will cause the options above to be used
if (options.Logging)
Engine.Logger.Info("Logging enabled.");
if (options.Tracing)
Engine.Logger.Info("Tracing enabled.");
if (options.SSLFix == true)
Engine.Logger.Info("SSL ECC workaround enabled.");
else if (options.SSLFix == false)
Engine.Logger.Info("SSL ECC workaround has been disabled.");
// Ignore SSL errors on Curl
Startup.IgnoreSslErrors = options.IgnoreSslErrors;
if (options.IgnoreSslErrors == true)
{
Engine.Logger.Info("Curl will ignore SSL certificate errors.");
}
// Choose Data Folder
if (!string.IsNullOrWhiteSpace(options.DataFolder))
{
Startup.CustomDataFolder = options.DataFolder.Replace("\"", string.Empty).Replace("'", string.Empty).Replace(@"\\", @"\");
Engine.Logger.Info("Jackett Data will be stored in: " + Startup.CustomDataFolder);
}
/* ====== Actions ===== */
// Install service
if (options.Install)
{
Engine.ServiceConfig.Install();
return;
}
// Uninstall service
if (options.Uninstall)
{
Engine.Server.ReserveUrls(doInstall: false);
Engine.ServiceConfig.Uninstall();
return;
}
// Reserve urls
if (options.ReserveUrls)
{
Engine.Server.ReserveUrls(doInstall: true);
return;
}
// Start Service
if (options.StartService)
{
if (!Engine.ServiceConfig.ServiceRunning())
{
Engine.ServiceConfig.Start();
}
return;
}
// Stop Service
if (options.StopService)
{
if (Engine.ServiceConfig.ServiceRunning())
{
Engine.ServiceConfig.Stop();
}
return;
}
// Migrate settings
if (options.MigrateSettings)
{
Engine.ConfigService.PerformMigration();
return;
}
// Show Version
if (options.ShowVersion)
{
Console.WriteLine("Jackett v" + Engine.ConfigService.GetVersion());
return;
}
/* ====== Overrides ===== */
// Override listen public
if (options.ListenPublic || options.ListenPrivate)
{
if (Engine.Server.Config.AllowExternal != options.ListenPublic)
{
Engine.Logger.Info("Overriding external access to " + options.ListenPublic);
Engine.Server.Config.AllowExternal = options.ListenPublic;
if (System.Environment.OSVersion.Platform != PlatformID.Unix)
{
if (ServerUtil.IsUserAdministrator())
{
Engine.Server.ReserveUrls(doInstall: true);
}
else
{
Engine.Logger.Error("Unable to switch to public listening without admin rights.");
Environment.ExitCode = 1;
return;
}
}
Engine.Server.SaveConfig();
}
}
// Override port
if (options.Port != 0)
{
if (Engine.Server.Config.Port != options.Port)
{
Engine.Logger.Info("Overriding port to " + options.Port);
Engine.Server.Config.Port = options.Port;
if (System.Environment.OSVersion.Platform != PlatformID.Unix)
{
if (ServerUtil.IsUserAdministrator())
{
Engine.Server.ReserveUrls(doInstall: true);
}
else
{
Engine.Logger.Error("Unable to switch ports when not running as administrator");
Environment.ExitCode = 1;
return;
}
}
Engine.Server.SaveConfig();
}
}
}
Engine.Server.Initalize();
Engine.Server.Start();
Engine.RunTime.Spin();
Engine.Logger.Info("Server thread exit");
}
catch (Exception e)
{
Engine.Logger.Error(e, "Top level exception");
}
}
}
}

View File

@@ -39,6 +39,10 @@
<ApplicationIcon>jackett.ico</ApplicationIcon>
</PropertyGroup>
<ItemGroup>
<Reference Include="Autofac, Version=3.5.0.0, Culture=neutral, PublicKeyToken=17863af14b0044da, processorArchitecture=MSIL">
<HintPath>..\packages\Autofac.3.5.2\lib\net40\Autofac.dll</HintPath>
<Private>True</Private>
</Reference>
<Reference Include="CommandLine, Version=1.9.71.2, Culture=neutral, PublicKeyToken=de6f01bd326f8c32, processorArchitecture=MSIL">
<HintPath>..\packages\CommandLineParser.1.9.71\lib\net45\CommandLine.dll</HintPath>
<Private>True</Private>

View File

@@ -1,149 +1,150 @@
using CommandLine;
using Jackett.Services;
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
namespace Jackett.Updater
{
class Program
{
static void Main(string[] args)
{
new Program().Run(args);
}
private void Run(string[] args)
{
Engine.Logger.Info("Jackett Updater v" + GetCurrentVersion());
try {
var options = new UpdaterConsoleOptions();
if (Parser.Default.ParseArguments(args, options))
{
ProcessUpdate(options);
}
else
{
Engine.Logger.Error("Failed to process update arguments!: " + string.Join(" ", args));
Console.ReadKey();
}
}
catch (Exception e)
{
Engine.Logger.Error(e, "Exception applying update!");
}
}
private string GetCurrentVersion()
{
var assembly = System.Reflection.Assembly.GetExecutingAssembly();
var fvi = FileVersionInfo.GetVersionInfo(assembly.Location);
return fvi.FileVersion;
}
private void ProcessUpdate(UpdaterConsoleOptions options)
{
var updateLocation = GetUpdateLocation();
if(!(updateLocation.EndsWith("\\") || updateLocation.EndsWith("/")))
{
updateLocation += Path.DirectorySeparatorChar;
}
var isWindows = System.Environment.OSVersion.Platform != PlatformID.Unix;
var trayRunning = false;
var trayProcesses = Process.GetProcessesByName("JackettTray");
if (isWindows)
{
if (trayProcesses.Count() > 0)
{
foreach (var proc in trayProcesses)
{
try
{
Engine.Logger.Info("Killing tray process " + proc.Id);
proc.Kill();
trayRunning = true;
}
catch { }
}
}
}
Engine.Logger.Info("Waiting for Jackett to close..");
Thread.Sleep(2000);
var files = Directory.GetFiles(updateLocation, "*.*", SearchOption.AllDirectories);
foreach(var file in files)
{
var fileName = Path.GetFileName(file).ToLowerInvariant();
if (fileName.EndsWith(".zip") ||
fileName.EndsWith(".tar") ||
fileName.EndsWith(".gz"))
{
continue;
}
try {
Engine.Logger.Info("Copying " + fileName);
var dest = Path.Combine(options.Path, file.Substring(updateLocation.Length));
File.Copy(file, dest, true);
}
catch(Exception e)
{
Engine.Logger.Error(e);
}
}
if (trayRunning)
{
var startInfo = new ProcessStartInfo()
{
Arguments = options.Args,
FileName = Path.Combine(options.Path, "JackettTray.exe"),
UseShellExecute = true
};
Process.Start(startInfo);
}
if(string.Equals(options.Type, "JackettService.exe", StringComparison.InvariantCultureIgnoreCase))
{
var serviceHelper = new ServiceConfigService(null, null);
if (serviceHelper.ServiceExists())
{
serviceHelper.Start();
}
} else
{
var startInfo = new ProcessStartInfo()
{
Arguments = options.Args,
FileName = Path.Combine(options.Path, "JackettConsole.exe"),
UseShellExecute = true
};
if (!isWindows)
{
startInfo.Arguments = startInfo.FileName + " " + startInfo.Arguments;
startInfo.FileName = "mono";
}
Engine.Logger.Info("Starting Jackett: " + startInfo.FileName + " " + startInfo.Arguments);
Process.Start(startInfo);
}
}
private string GetUpdateLocation()
{
var location = new Uri(Assembly.GetEntryAssembly().GetName().CodeBase);
return new FileInfo(location.AbsolutePath).DirectoryName;
}
}
}
using CommandLine;
using Jackett.Services;
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
namespace Jackett.Updater
{
class Program
{
static void Main(string[] args)
{
new Program().Run(args);
}
private void Run(string[] args)
{
Engine.SetupLogging(null, "updater.txt");
Engine.Logger.Info("Jackett Updater v" + GetCurrentVersion());
Engine.Logger.Info("Options " + string.Join(" ", args));
try {
var options = new UpdaterConsoleOptions();
if (Parser.Default.ParseArguments(args, options))
{
ProcessUpdate(options);
}
else
{
Engine.Logger.Error("Failed to process update arguments!: " + string.Join(" ", args));
Console.ReadKey();
}
}
catch (Exception e)
{
Engine.Logger.Error(e, "Exception applying update!");
}
}
private string GetCurrentVersion()
{
var assembly = System.Reflection.Assembly.GetExecutingAssembly();
var fvi = FileVersionInfo.GetVersionInfo(assembly.Location);
return fvi.FileVersion;
}
private void ProcessUpdate(UpdaterConsoleOptions options)
{
var updateLocation = GetUpdateLocation();
if(!(updateLocation.EndsWith("\\") || updateLocation.EndsWith("/")))
{
updateLocation += Path.DirectorySeparatorChar;
}
var isWindows = System.Environment.OSVersion.Platform != PlatformID.Unix;
var trayRunning = false;
var trayProcesses = Process.GetProcessesByName("JackettTray");
if (isWindows)
{
if (trayProcesses.Count() > 0)
{
foreach (var proc in trayProcesses)
{
try
{
Engine.Logger.Info("Killing tray process " + proc.Id);
proc.Kill();
trayRunning = true;
}
catch { }
}
}
}
Engine.Logger.Info("Waiting for Jackett to close..");
Thread.Sleep(2000);
Engine.Logger.Info("Finding files in: " + updateLocation);
var files = Directory.GetFiles(updateLocation, "*.*", SearchOption.AllDirectories);
foreach(var file in files)
{
var fileName = Path.GetFileName(file).ToLowerInvariant();
if (fileName.EndsWith(".zip") ||
fileName.EndsWith(".tar") ||
fileName.EndsWith(".gz"))
{
continue;
}
try {
Engine.Logger.Info("Copying " + fileName);
var dest = Path.Combine(options.Path, file.Substring(updateLocation.Length));
File.Copy(file, dest, true);
}
catch(Exception e)
{
Engine.Logger.Error(e);
}
}
if (trayRunning)
{
var startInfo = new ProcessStartInfo()
{
Arguments = options.Args,
FileName = Path.Combine(options.Path, "JackettTray.exe"),
UseShellExecute = true
};
Process.Start(startInfo);
}
if(string.Equals(options.Type, "JackettService.exe", StringComparison.InvariantCultureIgnoreCase))
{
var serviceHelper = new ServiceConfigService(null, null);
if (serviceHelper.ServiceExists())
{
serviceHelper.Start();
}
} else
{
var startInfo = new ProcessStartInfo()
{
Arguments = options.Args,
FileName = Path.Combine(options.Path, "JackettConsole.exe"),
UseShellExecute = true
};
if (!isWindows)
{
startInfo.Arguments = startInfo.FileName + " " + startInfo.Arguments;
startInfo.FileName = "mono";
}
Engine.Logger.Info("Starting Jackett: " + startInfo.FileName + " " + startInfo.Arguments);
Process.Start(startInfo);
}
}
private string GetUpdateLocation()
{
var location = new Uri(Assembly.GetEntryAssembly().GetName().CodeBase);
return new FileInfo(location.AbsolutePath).DirectoryName;
}
}
}

View File

@@ -1,5 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<packages>
<package id="Autofac" version="3.5.2" targetFramework="net45" />
<package id="CommandLineParser" version="1.9.71" targetFramework="net45" />
<package id="Microsoft.Bcl" version="1.1.10" targetFramework="net45" />
<package id="Microsoft.Bcl.Build" version="1.0.21" targetFramework="net45" />

View File

@@ -42,14 +42,17 @@
text-align: center;
}
#unconfigured-indexers .card {
width: 200px;
}
#unconfigured-indexers .card {
width: 200px;
position: relative;
}
.unconfigured-indexer {
height: 120px;
height: 70px;
}
.indexer {
height: 252px;
}
@@ -62,16 +65,20 @@
padding-bottom: 5px;
}
.indexer-logo > .hidden-name {
position: absolute;
color: rgba(255, 255, 255, 0);
left: 0;
}
.indexer-logo > .hidden-name {
position: absolute;
color: rgba(255, 255, 255, 0);
left: 0;
}
.indexer-logo > img {
width: 100%;
border-bottom: 1px solid #FFF;
}
.indexer-logo img {
width: 100%;
border-bottom: 1px solid #FFF;
}
#unconfigured-indexers .indexer-logo img {
cursor: pointer;
}
.indexer-name > h3 {
margin-top: 13px;
@@ -82,10 +89,9 @@
text-align: center;
}
.indexer-buttons > .btn {
margin-bottom: 2px;
}
.indexer-buttons > .btn {
margin-bottom: 2px;
}
.indexer-button-test {
width: 60px;
@@ -287,3 +293,7 @@ pre {
width: 80px;
}
.setup-item-displayinfo:empty {
display: none;
}

View File

@@ -1,15 +1,17 @@
$(document).ready(function () {
var basePath = '';
$(document).ready(function () {
$.ajaxSetup({ cache: false });
window.jackettIsLocal = window.location.hostname === 'localhost' ||
window.location.hostname === '127.0.0.1';
bindUIButtons();
reloadIndexers();
loadJackettSettings();
});
function getJackettConfig(callback) {
var jqxhr = $.get("/admin/get_jackett_config", function (data) {
var jqxhr = $.get("get_jackett_config", function (data) {
callback(data);
}).fail(function () {
@@ -22,6 +24,12 @@ function loadJackettSettings() {
$("#api-key-input").val(data.config.api_key);
$("#app-version").html(data.app_version);
$("#jackett-port").val(data.config.port);
$("#jackett-basepathoverride").val(data.config.basepathoverride);
basePath = data.config.basepathoverride;
if (basePath === null || basePath === undefined) {
basePath = '';
}
$("#jackett-savedir").val(data.config.blackholedir);
$("#jackett-allowext").attr('checked', data.config.external);
$("#jackett-allowupdate").attr('checked', data.config.updatedisabled);
@@ -32,6 +40,8 @@ function loadJackettSettings() {
if (password != null && password != '') {
$("#logoutBtn").show();
}
reloadIndexers();
});
}
@@ -39,7 +49,7 @@ function reloadIndexers() {
$('#indexers').hide();
$('#indexers > .indexer').remove();
$('#unconfigured-indexers').empty();
var jqxhr = $.get("/admin/get_indexers", function (data) {
var jqxhr = $.get("get_indexers", function (data) {
displayIndexers(data.items);
}).fail(function () {
doNotify("Error loading indexers, request to Jackett server failed", "danger", "glyphicon glyphicon-alert");
@@ -52,8 +62,8 @@ function displayIndexers(items) {
$('#unconfigured-indexers-template').empty();
for (var i = 0; i < items.length; i++) {
var item = items[i];
item.torznab_host = resolveUrl("/torznab/" + item.id);
item.potato_host = resolveUrl("/potato/" + item.id);
item.torznab_host = resolveUrl(basePath + "/torznab/" + item.id);
item.potato_host = resolveUrl(basePath + "/potato/" + item.id);
if (item.configured)
$('#indexers').append(indexerTemplate(item));
else
@@ -72,9 +82,10 @@ function displayIndexers(items) {
$('.indexer-setup').each(function (i, btn) {
var $btn = $(btn);
var id = $btn.data("id");
var link = $btn.data("link");
$btn.click(function () {
$('#select-indexer-modal').modal('hide').on('hidden.bs.modal', function (e) {
displayIndexerSetup(id);
displayIndexerSetup(id, link);
});
});
});
@@ -91,7 +102,7 @@ function prepareDeleteButtons() {
var $btn = $(btn);
var id = $btn.data("id");
$btn.click(function () {
var jqxhr = $.post("/admin/delete_indexer", JSON.stringify({ indexer: id }), function (data) {
var jqxhr = $.post("delete_indexer", JSON.stringify({ indexer: id }), function (data) {
if (data.result == "error") {
doNotify("Delete error for " + id + "\n" + data.error, "danger", "glyphicon glyphicon-alert");
}
@@ -111,8 +122,9 @@ function prepareSetupButtons() {
$('.indexer-setup').each(function (i, btn) {
var $btn = $(btn);
var id = $btn.data("id");
var link = $btn.data("link");
$btn.click(function () {
displayIndexerSetup(id);
displayIndexerSetup(id, link);
});
});
}
@@ -123,7 +135,7 @@ function prepareTestButtons() {
var id = $btn.data("id");
$btn.click(function () {
doNotify("Test started for " + id, "info", "glyphicon glyphicon-transfer");
var jqxhr = $.post("/admin/test_indexer", JSON.stringify({ indexer: id }), function (data) {
var jqxhr = $.post("test_indexer", JSON.stringify({ indexer: id }), function (data) {
if (data.result == "error") {
doNotify("Test failed for " + id + ": \n" + data.error, "danger", "glyphicon glyphicon-alert");
}
@@ -137,15 +149,15 @@ function prepareTestButtons() {
});
}
function displayIndexerSetup(id) {
function displayIndexerSetup(id, link) {
var jqxhr = $.post("/admin/get_config_form", JSON.stringify({ indexer: id }), function (data) {
var jqxhr = $.post("get_config_form", JSON.stringify({ indexer: id }), function (data) {
if (data.result == "error") {
doNotify("Error: " + data.error, "danger", "glyphicon glyphicon-alert");
return;
}
populateSetupForm(id, data.name, data.config, data.caps);
populateSetupForm(id, data.name, data.config, data.caps, link);
}).fail(function () {
doNotify("Request to Jackett server failed", "danger", "glyphicon glyphicon-alert");
@@ -196,9 +208,9 @@ function populateConfigItems(configForm, config) {
}
}
function newConfigModal(title, config, caps) {
function newConfigModal(title, config, caps, link) {
var configTemplate = Handlebars.compile($("#jackett-config-setup-modal").html());
var configForm = $(configTemplate({ title: title, caps: caps }));
var configForm = $(configTemplate({ title: title, caps: caps, link:link }));
$("#modals").append(configForm);
populateConfigItems(configForm, config);
return configForm;
@@ -234,8 +246,8 @@ function getConfigModalJson(configForm) {
return configJson;
}
function populateSetupForm(indexerId, name, config, caps) {
var configForm = newConfigModal(name, config, caps);
function populateSetupForm(indexerId, name, config, caps, link) {
var configForm = newConfigModal(name, config, caps, link);
var $goButton = configForm.find(".setup-indexer-go");
$goButton.click(function () {
var data = { indexer: indexerId, name: name };
@@ -245,7 +257,7 @@ function populateSetupForm(indexerId, name, config, caps) {
$goButton.prop('disabled', true);
$goButton.html($('#spinner').html());
var jqxhr = $.post("/admin/configure_indexer", JSON.stringify(data), function (data) {
var jqxhr = $.post("configure_indexer", JSON.stringify(data), function (data) {
if (data.result == "error") {
if (data.config) {
populateConfigItems(configForm, data.config);
@@ -319,7 +331,7 @@ function bindUIButtons() {
});
$("#jackett-show-releases").click(function () {
var jqxhr = $.get("/admin/GetCache", function (data) {
var jqxhr = $.get("GetCache", function (data) {
var releaseTemplate = Handlebars.compile($("#jackett-releases").html());
var item = { releases: data, Title: 'Releases' };
var releaseDialog = $(releaseTemplate(item));
@@ -401,7 +413,7 @@ function bindUIButtons() {
$("#jackett-show-search").click(function () {
$('#select-indexer-modal').remove();
var jqxhr = $.get("/admin/get_indexers", function (data) {
var jqxhr = $.get("get_indexers", function (data) {
var scope = {
items: data.items
};
@@ -443,6 +455,13 @@ function bindUIButtons() {
setCategories(trackerId, this.items);
}, scope));
document.getElementById("searchquery")
.addEventListener("keyup", function (event) {
event.preventDefault();
if (event.keyCode == 13) {
document.getElementById("jackett-search-perform").click();
}
});
$('#jackett-search-perform').click(function () {
if ($('#jackett-search-perform').text().trim() !== 'Search trackers') {
@@ -457,7 +476,7 @@ function bindUIButtons() {
$('#searchResults').empty();
$('#jackett-search-perform').html($('#spinner').html());
var jqxhr = $.post("/admin/search", queryObj, function (data) {
var jqxhr = $.post("search", queryObj, function (data) {
$('#jackett-search-perform').html('Search trackers');
var resultsTemplate = Handlebars.compile($("#jackett-search-results").html());
var results = $('#searchResults');
@@ -532,7 +551,7 @@ function bindUIButtons() {
});
$("#view-jackett-logs").click(function () {
var jqxhr = $.get("/admin/GetLogs", function (data) {
var jqxhr = $.get("GetLogs", function (data) {
var releaseTemplate = Handlebars.compile($("#jackett-logs").html());
var item = { logs: data };
var releaseDialog = $(releaseTemplate(item));
@@ -546,6 +565,7 @@ function bindUIButtons() {
$("#change-jackett-port").click(function () {
var jackett_port = $("#jackett-port").val();
var jackett_basepathoverride = $("#jackett-basepathoverride").val();
var jackett_external = $("#jackett-allowext").is(':checked');
var jackett_update = $("#jackett-allowupdate").is(':checked');
var jackett_prerelease = $("#jackett-prerelease").is(':checked');
@@ -556,21 +576,17 @@ function bindUIButtons() {
updatedisabled: jackett_update,
prerelease: jackett_prerelease,
blackholedir: $("#jackett-savedir").val(),
logging: jackett_logging
logging: jackett_logging,
basepathoverride: jackett_basepathoverride
};
var jqxhr = $.post("/admin/set_config", JSON.stringify(jsonObject), function (data) {
var jqxhr = $.post("set_config", JSON.stringify(jsonObject), function (data) {
if (data.result == "error") {
doNotify("Error: " + data.error, "danger", "glyphicon glyphicon-alert");
return;
} else {
doNotify("Redirecting you to complete configuration update..", "success", "glyphicon glyphicon-ok");
window.setTimeout(function () {
url = window.location.href;
if (data.external) {
window.location.href = url.substr(0, url.lastIndexOf(":") + 1) + data.port;
} else {
window.location.href = 'http://127.0.0.1:' + data.port;
}
window.location.reload(true);
}, 3000);
}
@@ -580,7 +596,7 @@ function bindUIButtons() {
});
$("#trigger-updater").click(function () {
var jqxhr = $.get("/admin/trigger_update", function (data) {
var jqxhr = $.get("trigger_update", function (data) {
if (data.result == "error") {
doNotify("Error: " + data.error, "danger", "glyphicon glyphicon-alert");
return;
@@ -596,7 +612,7 @@ function bindUIButtons() {
var password = $("#jackett-adminpwd").val();
var jsonObject = { password: password };
var jqxhr = $.post("/admin/set_admin_password", JSON.stringify(jsonObject), function (data) {
var jqxhr = $.post("set_admin_password", JSON.stringify(jsonObject), function (data) {
if (data.result == "error") {
doNotify("Error: " + data.error, "danger", "glyphicon glyphicon-alert");

View File

@@ -3,28 +3,28 @@
<html lang="en" xmlns="http://www.w3.org/1999/xhtml">
<head>
<meta charset="utf-8" />
<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>
<script src="/libs/jquery.dataTables.min.js"></script>
<script src="/libs/handlebars.min.js"></script>
<script src="/libs/moment.min.js"></script>
<script src="/libs/handlebarsmoment.js"></script>
<script src="/bootstrap/bootstrap.min.js"></script>
<script src="/libs/bootstrap-notify.js"></script>
<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>
<script src="../libs/jquery.dataTables.min.js"></script>
<script src="../libs/handlebars.min.js"></script>
<script src="../libs/moment.min.js"></script>
<script src="../libs/handlebarsmoment.js"></script>
<script src="../bootstrap/bootstrap.min.js"></script>
<script src="../libs/bootstrap-notify.js"></script>
<script src="https://www.google.com/recaptcha/api.js?onload=onloadCallback&render=explicit" async defer></script>
<link href="/bootstrap/bootstrap.min.css" rel="stylesheet">
<link href="/animate.css" rel="stylesheet">
<link href="/custom.css" rel="stylesheet">
<link href="/css/jquery.dataTables.css" rel="stylesheet">
<link rel="stylesheet" href="/css/font-awesome.min.css">
<link href="../bootstrap/bootstrap.min.css" rel="stylesheet">
<link href="../animate.css" rel="stylesheet">
<link href="../custom.css" rel="stylesheet">
<link href="../css/jquery.dataTables.css" rel="stylesheet">
<link rel="stylesheet" href="../css/font-awesome.min.css">
<title>Jackett</title>
</head>
<body>
<div id="page">
<img id="logo" src="/jacket_medium.png" alt="Logo" /><span id="header-title">Jackett</span>
<img id="logo" src="../jacket_medium.png" alt="Logo" /><span id="header-title">Jackett</span>
<div class="pull-right jackett-apikey">
<span class="input-header">API Key: </span>
@@ -48,7 +48,7 @@
<ol>
<li>Go to <b>Settings > Indexers > Add > Torznab > Custom</b>.</li>
<li>For <b>URL</b> enter the <b>Torznab Host</b> of one of the indexers.</li>
<li>For the <b>API key</b> using the key below.</li>
<li>For the <b>API key</b> using the key above.</li>
</ol>
<h4>Adding a Jackett indexer in CouchPotato</h4>
<ol>
@@ -84,10 +84,13 @@
Logout
</a>
</div>
<div class="input-area">
<span class="input-header">Base Path Override: </span>
<input id="jackett-basepathoverride" class="form-control input-right" type="text" value="" placeholder="/jackett/">
</div>
<div class="input-area">
<span class="input-header">Server port: </span>
<input id="jackett-port" class="form-control input-right" type="text" value="" placeholder="9117">
</div>
<div class="input-area">
<span class="input-header">Manual download blackhole directory: </span>
@@ -172,10 +175,10 @@
<div class="indexer-logo">
<!-- Make section browser searchable -->
<span class="hidden-name">{{name}}</span>
<img alt="{{name}}" title="{{name}}" src="/logos/{{id}}.png" />
<img alt="{{name}}" title="{{name}}" src="../logos/{{id}}.png" />
</div>
<div class="indexer-buttons">
<button class="btn btn-primary btn-sm indexer-setup" data-id="{{id}}">
<button class="btn btn-primary btn-sm indexer-setup" data-id="{{id}}" data-link="{{site_link}}">
<span class="glyphicon glyphicon-wrench" aria-hidden="true"></span>
</button>
<button class="btn btn-danger btn-sm indexer-button-delete" data-id="{{id}}">
@@ -203,14 +206,11 @@
</script>
<script id="unconfigured-indexer" type="text/x-handlebars-template">
<div class="unconfigured-indexer card">
<div class="indexer-logo">
<div class="indexer-logo indexer-setup" data-id="{{id}}" data-link="{{site_link}}">
<!-- Make section browser searchable -->
<span class="hidden-name">{{name}}</span>
<img alt="{{name}}" title="{{name}}" src="/logos/{{id}}.png" />
</div>
<div class="indexer-buttons">
<a class="btn btn-info" target="_blank" href="{{site_link}}">Visit <span class="glyphicon glyphicon-new-window" aria-hidden="true"></span></a>
<button class="indexer-setup btn btn-success" data-id="{{id}}">Setup <span class="glyphicon glyphicon-ok" aria-hidden="true"></span></button>
<img alt="{{name}}" title="{{name}}" src="../logos/{{id}}.png" />
</div>
</div>
</script>
@@ -437,7 +437,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}}</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>
@@ -481,6 +481,6 @@
<span class="spinner glyphicon glyphicon-refresh"></span>
</script>
<script src="/custom.js"></script>
<script src="../custom.js"></script>
</body>
</html>

View File

@@ -4,30 +4,30 @@
<head>
<meta charset="utf-8" />
<link rel='shortcut icon' type='image/x-icon' href='/favicon.ico' />
<link rel='shortcut icon' type='image/x-icon' href='../favicon.ico' />
<script src="/libs/jquery.min.js"></script>
<script src="/libs/jquery.dataTables.min.js"></script>
<script src="/libs/handlebars.min.js"></script>
<script src="/libs/moment.min.js"></script>
<script src="/libs/handlebarsmoment.js"></script>
<script src="/bootstrap/bootstrap.min.js"></script>
<script src="/libs/bootstrap-notify.js"></script>
<script src="../libs/jquery.min.js"></script>
<script src="../libs/jquery.dataTables.min.js"></script>
<script src="../libs/handlebars.min.js"></script>
<script src="../libs/moment.min.js"></script>
<script src="../libs/handlebarsmoment.js"></script>
<script src="../bootstrap/bootstrap.min.js"></script>
<script src="../libs/bootstrap-notify.js"></script>
<link href="/bootstrap/bootstrap.min.css" rel="stylesheet">
<link href="/animate.css" rel="stylesheet">
<link href="/custom.css" rel="stylesheet">
<link href="../bootstrap/bootstrap.min.css" rel="stylesheet">
<link href="../animate.css" rel="stylesheet">
<link href="../custom.css" rel="stylesheet">
<title>Jackett</title>
</head>
<body>
<div id="page">
<img id="logo" src="/jacket_medium.png" /><span id="header-title">Jackett</span>
<img id="logo" src="../jacket_medium.png" /><span id="header-title">Jackett</span>
<hr />
<h1>Login</h1>
<form action="/Admin/Dashboard" method="post">
<form action="Dashboard" method="post">
<div class="input-area">
<span class="input-header">Admin password</span>
<input id="password" name="password" class="form-control input-right" type="password">
@@ -38,4 +38,4 @@
</form>
</div>
</body>
</html>
</html>

Binary file not shown.

After

Width:  |  Height:  |  Size: 26 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 38 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 19 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 22 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 29 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 19 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 17 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 24 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 21 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 51 KiB

View File

@@ -80,7 +80,7 @@ namespace Jackett.Controllers
var ctx = Request.GetOwinContext();
var authManager = ctx.Authentication;
authManager.SignOut("ApplicationCookie");
return Redirect("/Admin/Dashboard");
return Redirect("Admin/Dashboard");
}
[HttpGet]
@@ -318,6 +318,7 @@ namespace Jackett.Controllers
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;
@@ -349,9 +350,12 @@ namespace Jackett.Controllers
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);
@@ -446,7 +450,7 @@ namespace Jackett.Controllers
private void ConfigureCacheResults(List<TrackerCacheResult> results)
{
var serverUrl = string.Format("{0}://{1}:{2}/", Request.RequestUri.Scheme, Request.RequestUri.Host, Request.RequestUri.Port);
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;

View File

@@ -99,7 +99,8 @@ namespace Jackett.Controllers
{
ApiKey = request.passkey,
Categories = MOVIE_CATS,
SearchTerm = request.search
SearchTerm = request.search,
ImdbID = request.imdbid
};
IEnumerable<ReleaseInfo> releases = new List<ReleaseInfo>();
@@ -117,7 +118,7 @@ namespace Jackett.Controllers
}
releases = indexer.FilterResults(torznabQuery, releases);
var serverUrl = string.Format("{0}://{1}:{2}/", Request.RequestUri.Scheme, Request.RequestUri.Host, Request.RequestUri.Port);
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);

View File

@@ -100,7 +100,7 @@ namespace Jackett.Controllers
logger.Info(logBuilder.ToString());
var serverUrl = string.Format("{0}://{1}:{2}/", Request.RequestUri.Scheme, Request.RequestUri.Host, Request.RequestUri.Port);
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,

View File

@@ -85,6 +85,7 @@ namespace Jackett
using (var easy = new CurlEasy())
{
easy.Url = curlRequest.Url;
easy.BufferSize = 64 * 1024;
easy.UserAgent = BrowserUtil.ChromeUserAgent;
@@ -139,7 +140,12 @@ namespace Jackett
{
easy.SetOpt(CurlOption.SslVerifyhost, false);
easy.SetOpt(CurlOption.SslVerifyPeer, false);
}
}
if (Startup.ProxyConnection != null)
{
easy.SetOpt(CurlOption.Proxy, Startup.ProxyConnection);
}
easy.Perform();
@@ -155,6 +161,15 @@ namespace Jackett
var headerBytes = Combine(headerBuffers.ToArray());
var headerString = Encoding.UTF8.GetString(headerBytes);
if (Startup.ProxyConnection != null)
{
var firstcrlf = headerString.IndexOf("\r\n\r\n");
var secondcrlf = headerString.IndexOf("\r\n\r\n", firstcrlf + 1);
if (secondcrlf > 0)
{
headerString = headerString.Substring(firstcrlf + 4, secondcrlf - (firstcrlf));
}
}
var headerParts = headerString.Split(new char[] { '\n', '\r' }, StringSplitOptions.RemoveEmptyEntries);
var headers = new List<string[]>();
var headerCount = 0;

View File

@@ -1,190 +1,191 @@
using Autofac;
using Jackett.Services;
using NLog;
using NLog.Config;
using NLog.LayoutRenderers;
using NLog.Targets;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Jackett
{
public class Engine
{
private static IContainer container = null;
static Engine()
{
BuildContainer();
}
public static void BuildContainer()
{
var builder = new ContainerBuilder();
builder.RegisterModule<JackettModule>();
container = builder.Build();
// Register the container in itself to allow for late resolves
var secondaryBuilder = new ContainerBuilder();
secondaryBuilder.RegisterInstance<IContainer>(container).SingleInstance();
SetupLogging(secondaryBuilder);
secondaryBuilder.Update(container);
}
public static IContainer GetContainer()
{
return container;
}
public static bool IsWindows
{
get
{
return Environment.OSVersion.Platform == PlatformID.Win32NT;
}
}
public static IConfigurationService ConfigService
{
get
{
return container.Resolve<IConfigurationService>();
}
}
public static IProcessService ProcessService
{
get
{
return container.Resolve<IProcessService>();
}
}
public static IServiceConfigService ServiceConfig
{
get
{
return container.Resolve<IServiceConfigService>();
}
}
public static ITrayLockService LockService
{
get
{
return container.Resolve<ITrayLockService>();
}
}
public static IServerService Server
{
get
{
return container.Resolve<IServerService>();
}
}
public static IRunTimeService RunTime
{
get
{
return container.Resolve<IRunTimeService>();
}
}
public static Logger Logger
{
get
{
return container.Resolve<Logger>();
}
}
public static ISecuityService SecurityService
{
get
{
return container.Resolve<ISecuityService>();
}
}
private static void SetupLogging(ContainerBuilder builder)
{
var logLevel = Startup.TracingEnabled ? LogLevel.Debug : LogLevel.Info;
// Add custom date time format renderer as the default is too long
ConfigurationItemFactory.Default.LayoutRenderers.RegisterDefinition("simpledatetime", typeof(SimpleDateTimeRenderer));
var logConfig = new LoggingConfiguration();
var logFile = new FileTarget();
logConfig.AddTarget("file", logFile);
logFile.Layout = "${longdate} ${level} ${message} ${exception:format=ToString}";
logFile.FileName = Path.Combine(ConfigurationService.GetAppDataFolderStatic(), "log.txt");
logFile.ArchiveFileName = "log.{#####}.txt";
logFile.ArchiveAboveSize = 500000;
logFile.MaxArchiveFiles = 5;
logFile.KeepFileOpen = false;
logFile.ArchiveNumbering = ArchiveNumberingMode.DateAndSequence;
var logFileRule = new LoggingRule("*", logLevel, logFile);
logConfig.LoggingRules.Add(logFileRule);
var logConsole = new ColoredConsoleTarget();
logConfig.AddTarget("console", logConsole);
logConsole.Layout = "${simpledatetime} ${level} ${message} ${exception:format=ToString}";
var logConsoleRule = new LoggingRule("*", logLevel, logConsole);
logConfig.LoggingRules.Add(logConsoleRule);
var logService = new LogCacheService();
logConfig.AddTarget("service", logService);
var serviceRule = new LoggingRule("*", logLevel, logService);
logConfig.LoggingRules.Add(serviceRule);
LogManager.Configuration = logConfig;
builder.RegisterInstance<Logger>(LogManager.GetCurrentClassLogger()).SingleInstance();
}
public static void SetLogLevel(LogLevel level)
{
foreach (var rule in LogManager.Configuration.LoggingRules)
{
if (level == LogLevel.Debug)
{
if (!rule.Levels.Contains(LogLevel.Debug))
{
rule.EnableLoggingForLevel(LogLevel.Debug);
}
}
else
{
if (rule.Levels.Contains(LogLevel.Debug))
{
rule.DisableLoggingForLevel(LogLevel.Debug);
}
}
}
LogManager.ReconfigExistingLoggers();
}
}
[LayoutRenderer("simpledatetime")]
public class SimpleDateTimeRenderer : LayoutRenderer
{
protected override void Append(StringBuilder builder, LogEventInfo logEvent)
{
builder.Append(DateTime.Now.ToString("MM-dd HH:mm:ss"));
}
}
}
using Autofac;
using Jackett.Services;
using NLog;
using NLog.Config;
using NLog.LayoutRenderers;
using NLog.Targets;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Jackett
{
public class Engine
{
private static IContainer container = null;
static Engine()
{
BuildContainer();
}
public static void BuildContainer()
{
var builder = new ContainerBuilder();
builder.RegisterModule<JackettModule>();
container = builder.Build();
// Register the container in itself to allow for late resolves
var secondaryBuilder = new ContainerBuilder();
secondaryBuilder.RegisterInstance<IContainer>(container).SingleInstance();
SetupLogging(secondaryBuilder);
secondaryBuilder.Update(container);
}
public static IContainer GetContainer()
{
return container;
}
public static bool IsWindows
{
get
{
return Environment.OSVersion.Platform == PlatformID.Win32NT;
}
}
public static IConfigurationService ConfigService
{
get
{
return container.Resolve<IConfigurationService>();
}
}
public static IProcessService ProcessService
{
get
{
return container.Resolve<IProcessService>();
}
}
public static IServiceConfigService ServiceConfig
{
get
{
return container.Resolve<IServiceConfigService>();
}
}
public static ITrayLockService LockService
{
get
{
return container.Resolve<ITrayLockService>();
}
}
public static IServerService Server
{
get
{
return container.Resolve<IServerService>();
}
}
public static IRunTimeService RunTime
{
get
{
return container.Resolve<IRunTimeService>();
}
}
public static Logger Logger
{
get
{
return container.Resolve<Logger>();
}
}
public static ISecuityService SecurityService
{
get
{
return container.Resolve<ISecuityService>();
}
}
public static void SetupLogging(ContainerBuilder builder = null, string logfile = "log.txt")
{
var logLevel = Startup.TracingEnabled ? LogLevel.Debug : LogLevel.Info;
// Add custom date time format renderer as the default is too long
ConfigurationItemFactory.Default.LayoutRenderers.RegisterDefinition("simpledatetime", typeof(SimpleDateTimeRenderer));
var logConfig = new LoggingConfiguration();
var logFile = new FileTarget();
logConfig.AddTarget("file", logFile);
logFile.Layout = "${longdate} ${level} ${message} ${exception:format=ToString}";
logFile.FileName = Path.Combine(ConfigurationService.GetAppDataFolderStatic(), logfile);
logFile.ArchiveFileName = "log.{#####}.txt";
logFile.ArchiveAboveSize = 500000;
logFile.MaxArchiveFiles = 5;
logFile.KeepFileOpen = false;
logFile.ArchiveNumbering = ArchiveNumberingMode.DateAndSequence;
var logFileRule = new LoggingRule("*", logLevel, logFile);
logConfig.LoggingRules.Add(logFileRule);
var logConsole = new ColoredConsoleTarget();
logConfig.AddTarget("console", logConsole);
logConsole.Layout = "${simpledatetime} ${level} ${message} ${exception:format=ToString}";
var logConsoleRule = new LoggingRule("*", logLevel, logConsole);
logConfig.LoggingRules.Add(logConsoleRule);
var logService = new LogCacheService();
logConfig.AddTarget("service", logService);
var serviceRule = new LoggingRule("*", logLevel, logService);
logConfig.LoggingRules.Add(serviceRule);
LogManager.Configuration = logConfig;
if (builder != null)
{
builder.RegisterInstance<Logger>(LogManager.GetCurrentClassLogger()).SingleInstance();
}
}
public static void SetLogLevel(LogLevel level)
{
foreach (var rule in LogManager.Configuration.LoggingRules)
{
if (level == LogLevel.Debug)
{
if (!rule.Levels.Contains(LogLevel.Debug))
{
rule.EnableLoggingForLevel(LogLevel.Debug);
}
}
else
{
if (rule.Levels.Contains(LogLevel.Debug))
{
rule.DisableLoggingForLevel(LogLevel.Debug);
}
}
}
LogManager.ReconfigExistingLoggers();
}
}
[LayoutRenderer("simpledatetime")]
public class SimpleDateTimeRenderer : LayoutRenderer
{
protected override void Append(StringBuilder builder, LogEventInfo logEvent)
{
builder.Append(DateTime.Now.ToString("MM-dd HH:mm:ss"));
}
}
}

View File

@@ -0,0 +1,870 @@
using System;
using System.Collections.Generic;
using System.Collections.Specialized;
using System.Linq;
using System.Reflection;
using System.Text.RegularExpressions;
using System.Threading.Tasks;
using System.Web;
using CsQuery;
using Jackett.Models;
using Jackett.Models.IndexerConfig.Bespoke;
using Jackett.Services;
using Jackett.Utils;
using Jackett.Utils.Clients;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using NLog;
namespace Jackett.Indexers
{
/// <summary>
/// Provider for Abnormal Private French Tracker
/// </summary>
public class Abnormal : BaseIndexer, IIndexer
{
private string LoginUrl { get { return SiteLink + "login.php"; } }
private string SearchUrl { get { return SiteLink + "torrents.php"; } }
private string TorrentCommentUrl { get { return TorrentDescriptionUrl; } }
private string TorrentDescriptionUrl { get { return SiteLink + "torrents.php?id="; } }
private string TorrentDownloadUrl { get { return SiteLink + "torrents.php?action=download&id={id}&authkey={auth_key}&torrent_pass={torrent_pass}"; } }
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 Dictionary<string, string> emulatedBrowserHeaders = new Dictionary<string, string>();
private CQ fDom = null;
private ConfigurationDataAbnormal ConfigData
{
get { return (ConfigurationDataAbnormal)configData; }
set { base.configData = value; }
}
public Abnormal(IIndexerManagerService i, IWebClient w, Logger l, IProtectionService ps)
: base(
name: "Abnormal",
description: "General French Private Tracker",
link: "https://abnormal.ws/",
caps: new TorznabCapabilities(),
manager: i,
client: w,
logger: l,
p: ps,
downloadBase: "https://abnormal.ws/torrents.php?action=download&id=",
configData: new ConfigurationDataAbnormal())
{
// Clean capabilities
TorznabCaps.Categories.Clear();
// Movies
AddCategoryMapping("MOVIE|DVDR", TorznabCatType.MoviesDVD); // DVDR
AddCategoryMapping("MOVIE|DVDRIP", TorznabCatType.MoviesSD); // DVDRIP
AddCategoryMapping("MOVIE|BDRIP", TorznabCatType.MoviesSD); // BDRIP
AddCategoryMapping("MOVIE|VOSTFR", TorznabCatType.MoviesOther); // VOSTFR
AddCategoryMapping("MOVIE|HD|720p", TorznabCatType.MoviesHD); // HD 720P
AddCategoryMapping("MOVIE|HD|1080p", TorznabCatType.MoviesHD); // HD 1080P
AddCategoryMapping("MOVIE|REMUXBR", TorznabCatType.MoviesBluRay); // REMUX BLURAY
AddCategoryMapping("MOVIE|FULLBR", TorznabCatType.MoviesBluRay); // FULL BLURAY
// Series
AddCategoryMapping("TV|SD|VOSTFR", TorznabCatType.TV); // SD VOSTFR
AddCategoryMapping("TV|HD|VOSTFR", TorznabCatType.TVHD); // HD VOSTFR
AddCategoryMapping("TV|SD|VF", TorznabCatType.TVSD); // SD VF
AddCategoryMapping("TV|HD|VF", TorznabCatType.TVHD); // HD VF
AddCategoryMapping("TV|PACK|FR", TorznabCatType.TVOTHER); // PACK FR
AddCategoryMapping("TV|PACK|VOSTFR", TorznabCatType.TVOTHER); // PACK VOSTFR
AddCategoryMapping("TV|EMISSIONS", TorznabCatType.TVOTHER); // EMISSIONS
// Anime
AddCategoryMapping("ANIME", TorznabCatType.TVAnime); // ANIME
// Documentaries
AddCategoryMapping("DOCS", TorznabCatType.TVDocumentary); // DOCS
// Music
AddCategoryMapping("MUSIC|FLAC", TorznabCatType.AudioLossless); // FLAC
AddCategoryMapping("MUSIC|MP3", TorznabCatType.AudioMP3); // MP3
AddCategoryMapping("MUSIC|CONCERT", TorznabCatType.AudioVideo); // CONCERT
// Other
AddCategoryMapping("PC|APP", TorznabCatType.PC); // PC
AddCategoryMapping("PC|GAMES", TorznabCatType.PCGames); // GAMES
AddCategoryMapping("EBOOKS", TorznabCatType.BooksEbook); // EBOOKS
}
/// <summary>
/// Configure our WiHD Provider
/// </summary>
/// <param name="configJson">Our params in Json</param>
/// <returns>Configuration state</returns>
public async Task<IndexerConfigurationStatus> ApplyConfiguration(JToken configJson)
{
// Retrieve config values set by Jackett's user
ConfigData.LoadValuesFromJson(configJson);
// Check & Validate Config
validateConfig();
// Setting our data for a better emulated browser (maximum security)
// TODO: Encoded Content not supported by Jackett at this time
// emulatedBrowserHeaders.Add("Accept-Encoding", "gzip, deflate");
// If we want to simulate a browser
if (ConfigData.Browser.Value) {
// Clean headers
emulatedBrowserHeaders.Clear();
// Inject headers
emulatedBrowserHeaders.Add("Accept", ConfigData.HeaderAccept.Value);
emulatedBrowserHeaders.Add("Accept-Language", ConfigData.HeaderAcceptLang.Value);
emulatedBrowserHeaders.Add("DNT", Convert.ToInt32(ConfigData.HeaderDNT.Value).ToString());
emulatedBrowserHeaders.Add("Upgrade-Insecure-Requests", Convert.ToInt32(ConfigData.HeaderUpgradeInsecure.Value).ToString());
emulatedBrowserHeaders.Add("User-Agent", ConfigData.HeaderUserAgent.Value);
}
// Getting login form to retrieve CSRF token
var myRequest = new Utils.Clients.WebRequest()
{
Url = LoginUrl
};
// Add our headers to request
myRequest.Headers = emulatedBrowserHeaders;
// Building login form data
var pairs = new Dictionary<string, string> {
{ "username", ConfigData.Username.Value },
{ "password", ConfigData.Password.Value },
{ "keeplogged", "1" },
{ "login", "Connexion" }
};
// Do the login
var request = new Utils.Clients.WebRequest(){
PostData = pairs,
Referer = LoginUrl,
Type = RequestType.POST,
Url = LoginUrl,
Headers = emulatedBrowserHeaders
};
// Perform loggin
latencyNow();
output("\nPerform loggin.. with " + LoginUrl);
var response = await webclient.GetString(request);
// Test if we are logged in
await ConfigureIfOK(response.Cookies, response.Cookies.Contains("session="), () =>
{
// Parse error page
CQ dom = response.Content;
string message = dom[".warning"].Text().Split('.').Reverse().Skip(1).First();
// Try left
string left = dom[".info"].Text().Trim();
// Oops, unable to login
output("-> Login failed: \"" + message + "\" and " + left + " tries left before being banned for 6 hours !", "error");
throw new ExceptionWithConfigData("Login failed: " + message, configData);
});
output("-> Login Success");
return IndexerConfigurationStatus.RequiresTesting;
}
/// <summary>
/// Execute our search query
/// </summary>
/// <param name="query">Query</param>
/// <returns>Releases</returns>
public async Task<IEnumerable<ReleaseInfo>> PerformQuery(TorznabQuery query)
{
var releases = new List<ReleaseInfo>();
var torrentRowList = new List<CQ>();
var searchTerm = query.GetQueryString();
var searchUrl = SearchUrl;
int nbResults = 0;
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))
{
lock (cache)
{
// Remove old cache items
CleanCache();
// Search in cache
var cachedResult = cache.Where(i => i.Query == searchTerm).FirstOrDefault();
if (cachedResult != null)
return cachedResult.Results.Select(s => (ReleaseInfo)s.Clone()).ToArray();
}
}
// Build our query
var request = buildQuery(searchTerm, query, searchUrl);
// Getting results & Store content
WebClientStringResult results = await queryExec(request);
fDom = results.Content;
try
{
// Find torrent rows
var firstPageRows = findTorrentRows();
// Add them to torrents list
torrentRowList.AddRange(firstPageRows.Select(fRow => fRow.Cq()));
// Check if there are pagination links at bottom
Boolean pagination = (fDom[".linkbox > a"].Length != 0);
// If pagination available
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 {
// Check if we have a minimum of one result
if (firstPageRows.Length >= 1)
{
// Retrieve total count on our alone page
nbResults = firstPageRows.Count();
pageLinkCount = 1;
}
else
{
output("\nNo result found for your query, please try another search term ...\n", "info");
// No result found for this query
return releases;
}
}
output("\nFound " + nbResults + " result(s) (+/- " + firstPageRows.Length + ") in " + pageLinkCount + " page(s) for this query !");
output("\nThere are " + firstPageRows.Length + " results on the first page !");
// If we have a term used for search and pagination result superior to one
if (!string.IsNullOrWhiteSpace(query.GetQueryString()) && pageLinkCount > 1)
{
// Starting with page #2
for (int i = 2; i <= Math.Min(Int32.Parse(ConfigData.Pages.Value), pageLinkCount); i++)
{
output("\nProcessing page #" + i);
// Request our page
latencyNow();
// Build our query
var pageRequest = buildQuery(searchTerm, query, searchUrl, i);
// Getting results & Store content
WebClientStringResult pageResults = await queryExec(pageRequest);
// Assign response
fDom = pageResults.Content;
// Process page results
var additionalPageRows = findTorrentRows();
// Add them to torrents list
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)
{
output("\n=>> Torrent #" + (releases.Count + 1));
// ID
int id = ParseUtil.CoerceInt(Regex.Match(tRow.Find("td:eq(1) > a").Attr("href").ToString(), @"\d+").Value);
output("ID: " + id);
// Release Name
string name = tRow.Find("td:eq(1) > a").Text().ToString();
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 + ")");
// Seeders
int seeders = ParseUtil.CoerceInt(Regex.Match(tRow.Find("td:eq(6)").Text(), @"\d+").Value);
output("Seeders: " + seeders);
// Leechers
int leechers = ParseUtil.CoerceInt(Regex.Match(tRow.Find("td:eq(7)").Text(), @"\d+").Value);
output("Leechers: " + leechers);
// Completed
int completed = ParseUtil.CoerceInt(Regex.Match(tRow.Find("td:eq(5)").Text(), @"\d+").Value);
output("Completed: " + completed);
// Size
string sizeStr = tRow.Find("td:eq(4)").Text().Replace("Go", "gb").Replace("Mo", "mb").Replace("Ko", "kb");
long size = ReleaseInfo.GetBytes(sizeStr);
output("Size: " + sizeStr + " (" + size + " bytes)");
// Publish DateToString
IList<string> clockList = tRow.Find("td:eq(2) > span").Text().Replace("Il y a", "").Split(',').Select(s => s.Trim()).Where(s => s != String.Empty).ToList();
var date = agoToDate(clockList);
output("Released on: " + date.ToLocalTime());
// Torrent Details URL
Uri detailsLink = new Uri(TorrentDescriptionUrl + id);
output("Details: " + detailsLink.AbsoluteUri);
// Torrent Comments URL
Uri commentsLink = new Uri(TorrentCommentUrl + id);
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);
// 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;
releases.Add(release);
}
}
catch (Exception ex)
{
OnParseError("Error, unable to parse result \n" + ex.StackTrace, ex);
}
// Return found releases
return releases;
}
/// <summary>
/// Build query to process
/// </summary>
/// <param name="term">Term to search</param>
/// <param name="query">Torznab Query for categories mapping</param>
/// <param name="url">Search url for provider</param>
/// <param name="page">Page number to request</param>
/// <returns>URL to query for parsing and processing results</returns>
private string buildQuery(string term, TorznabQuery query, string url, int page = 0)
{
var parameters = new NameValueCollection();
List<string> categoriesList = MapTorznabCapsToTrackers(query);
string categories = null;
// Check if we are processing a new page
if (page > 0)
{
// Adding page number to query
parameters.Add("page", page.ToString());
}
// Loop on Categories needed
foreach (string category in categoriesList)
{
// If last, build !
if (categoriesList.Last() == category)
{
// Adding previous categories to URL with latest category
parameters.Add(Uri.EscapeDataString("cat[]"), HttpUtility.UrlEncode(category) + categories);
}
else
{
// Build categories parameter
categories += "&" + Uri.EscapeDataString("cat[]") + "=" + HttpUtility.UrlEncode(category);
}
}
// If search term provided
if (!string.IsNullOrWhiteSpace(term))
{
// Add search term
parameters.Add("search", HttpUtility.UrlEncode(term));
}
else
{
parameters.Add("search", HttpUtility.UrlEncode("%"));
// Showing all torrents (just for output function)
term = "all";
}
// Building our query -- Cannot use GetQueryString due to UrlEncode (generating wrong cat[] param)
url += "?" + string.Join("&", parameters.AllKeys.Select(a => a + "=" + parameters[a]));
output("\nBuilded query for \"" + term + "\"... " + url);
// Return our search url
return url;
}
/// <summary>
/// Switch Method for Querying
/// </summary>
/// <param name="request">URL created by Query Builder</param>
/// <returns>Results from query</returns>
private async Task<WebClientStringResult> queryExec(string request)
{
WebClientStringResult results = null;
// Switch in we are in DEV mode with Hard Drive Cache or not
if (DevMode && CacheMode)
{
// Check Cache before querying and load previous results if available
results = await queryCache(request);
}
else
{
// Querying tracker directly
results = await queryTracker(request);
}
return results;
}
/// <summary>
/// Get Torrents Page from Cache by Query Provided
/// </summary>
/// <param name="request">URL created by Query Builder</param>
/// <returns>Results from query</returns>
private async Task<WebClientStringResult> queryCache(string request)
{
WebClientStringResult results = null;
// Create Directory if not exist
System.IO.Directory.CreateDirectory(directory);
// Clean Storage Provider Directory from outdated cached queries
cleanCacheStorage();
// Create fingerprint for request
string file = directory + request.GetHashCode() + ".json";
// Checking modes states
if (System.IO.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));
}
else
{
// No cached file found, querying tracker directly
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));
}
return results;
}
/// <summary>
/// Get Torrents Page from Tracker by Query Provided
/// </summary>
/// <param name="request">URL created by Query Builder</param>
/// <returns>Results from query</returns>
private async Task<WebClientStringResult> queryTracker(string request)
{
WebClientStringResult results = null;
// Cache mode not enabled or cached file didn't exist for our query
output("\nQuerying tracker for results....");
// Request our first page
latencyNow();
results = await RequestStringWithCookiesAndRetry(request, null, null, emulatedBrowserHeaders);
// Return results from tracker
return results;
}
/// <summary>
/// Clean Hard Drive Cache Storage
/// </summary>
/// <param name="force">Force Provider Folder deletion</param>
private void cleanCacheStorage(Boolean force = false)
{
// Check cleaning method
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))
{
// Delete storage directory of provider
System.IO.Directory.Delete(directory, true);
output("-> Storage folder deleted successfully.");
}
else
{
// No directory, so nothing to do
output("-> No Storage folder found for this provider !");
}
}
else
{
int 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))
.Where(f => f.LastAccessTime < DateTime.Now.AddMilliseconds(-Convert.ToInt32(ConfigData.HardDriveCacheKeepTime.Value)))
.ToList()
.ForEach(f => {
output("Deleting cached file << " + f.Name + " >> ... done.");
f.Delete();
i++;
});
// Inform on what was cleaned during process
if(i > 0) {
output("-> Deleted " + i + " cached files during cleaning.");
}
else {
output("-> Nothing deleted during cleaning.");
}
}
}
/// <summary>
/// Generate a random fake latency to avoid detection on tracker side
/// </summary>
private void latencyNow()
{
// Need latency ?
if(Latency)
{
// Generate a random value in our range
var random = new Random(DateTime.Now.Millisecond);
int waiting = random.Next(Convert.ToInt32(ConfigData.LatencyStart.Value), Convert.ToInt32(ConfigData.LatencyEnd.Value));
output("\nLatency Faker => Sleeping for " + waiting + " ms...");
// Sleep now...
System.Threading.Thread.Sleep(waiting);
}
}
/// <summary>
/// Find torrent rows in search pages
/// </summary>
/// <returns>JQuery Object</returns>
private CQ findTorrentRows()
{
// Return all occurencis of torrents found
return fDom[".torrent_table > tbody > tr"].Not(".colhead");
}
/// <summary>
/// Convert Ago date to DateTime
/// </summary>
/// <param name="clockList"></param>
/// <returns>A DateTime</returns>
private DateTime agoToDate(IList<string> clockList)
{
DateTime release = DateTime.Now;
foreach (var ago in clockList)
{
// Check for years
if (ago.Contains("années") || ago.Contains("année"))
{
// Number of years to remove
int years = ParseUtil.CoerceInt(Regex.Match(ago.ToString(), @"\d+").Value);
// Removing
release = release.AddYears(-years);
continue;
}
// Check for months
else if (ago.Contains("mois"))
{
// Number of months to remove
int months = ParseUtil.CoerceInt(Regex.Match(ago.ToString(), @"\d+").Value);
// Removing
release = release.AddMonths(-months);
continue;
}
// Check for weeks
else if (ago.Contains("semaines") || ago.Contains("semaine"))
{
// Number of weeks to remove
int weeks = ParseUtil.CoerceInt(Regex.Match(ago.ToString(), @"\d+").Value);
// Removing
release = release.AddDays(-(7 * weeks));
continue;
}
// Check for days
else if (ago.Contains("jours") || ago.Contains("jour"))
{
// Number of days to remove
int days = ParseUtil.CoerceInt(Regex.Match(ago.ToString(), @"\d+").Value);
// Removing
release = release.AddDays(-days);
continue;
}
// Check for hours
else if (ago.Contains("heures") || ago.Contains("heure"))
{
// Number of hours to remove
int hours = ParseUtil.CoerceInt(Regex.Match(ago.ToString(), @"\d+").Value);
// Removing
release = release.AddHours(-hours);
continue;
}
// Check for minutes
else if (ago.Contains("mins") || ago.Contains("min"))
{
// Number of minutes to remove
int minutes = ParseUtil.CoerceInt(Regex.Match(ago.ToString(), @"\d+").Value);
// Removing
release = release.AddMinutes(-minutes);
continue;
}
// Check for seconds
else if (ago.Contains("secondes") || ago.Contains("seconde"))
{
// Number of seconds to remove
int seconds = ParseUtil.CoerceInt(Regex.Match(ago.ToString(), @"\d+").Value);
// Removing
release = release.AddSeconds(-seconds);
continue;
}
else
{
output("Unable to detect release date of torrent", "error");
//throw new Exception("Unable to detect release date of torrent");
}
}
return release;
}
/// <summary>
/// Output message for logging or developpment (console)
/// </summary>
/// <param name="message">Message to output</param>
/// <param name="level">Level for Logger</param>
private void output(string message, string level = "debug")
{
// Check if we are in dev mode
if(DevMode)
{
// Output message to console
Console.WriteLine(message);
}
else
{
// Send message to logger with level
switch (level)
{
default:
goto case "debug";
case "debug":
// Only if Debug Level Enabled on Jackett
if (Engine.Logger.IsDebugEnabled)
{
logger.Debug(message);
}
break;
case "info":
logger.Info(message);
break;
case "error":
logger.Error(message);
break;
}
}
}
/// <summary>
/// Validate Config entered by user on Jackett
/// </summary>
private void validateConfig()
{
output("\nValidating Settings ... \n");
// Check Username Setting
if (string.IsNullOrEmpty(ConfigData.Username.Value))
{
throw new ExceptionWithConfigData("You must provide a username for this tracker to login !", ConfigData);
}
else
{
output("Validated Setting -- Username (auth) => " + ConfigData.Username.Value.ToString());
}
// Check Password Setting
if (string.IsNullOrEmpty(ConfigData.Password.Value))
{
throw new ExceptionWithConfigData("You must provide a password with your username for this tracker to login !", ConfigData);
}
else
{
output("Validated Setting -- Password (auth) => " + ConfigData.Password.Value.ToString());
}
// Check Max Page Setting
if (!string.IsNullOrEmpty(ConfigData.Pages.Value))
{
try
{
output("Validated Setting -- Max Pages => " + Convert.ToInt32(ConfigData.Pages.Value));
}
catch (Exception)
{
throw new ExceptionWithConfigData("Please enter a numeric maximum number of pages to crawl !", ConfigData);
}
}
else
{
throw new ExceptionWithConfigData("Please enter a maximum number of pages to crawl !", ConfigData);
}
// Check Latency Setting
if (ConfigData.Latency.Value)
{
output("\nValidated Setting -- Latency Simulation enabled");
// Check Latency Start Setting
if (!string.IsNullOrEmpty(ConfigData.LatencyStart.Value))
{
try
{
output("Validated Setting -- Latency Start => " + Convert.ToInt32(ConfigData.LatencyStart.Value));
}
catch (Exception)
{
throw new ExceptionWithConfigData("Please enter a numeric latency start in ms !", ConfigData);
}
}
else
{
throw new ExceptionWithConfigData("Latency Simulation enabled, Please enter a start latency !", ConfigData);
}
// Check Latency End Setting
if (!string.IsNullOrEmpty(ConfigData.LatencyEnd.Value))
{
try
{
output("Validated Setting -- Latency End => " + Convert.ToInt32(ConfigData.LatencyEnd.Value));
}
catch (Exception)
{
throw new ExceptionWithConfigData("Please enter a numeric latency end in ms !", ConfigData);
}
}
else
{
throw new ExceptionWithConfigData("Latency Simulation enabled, Please enter a end latency !", ConfigData);
}
}
// Check Browser Setting
if (ConfigData.Browser.Value)
{
output("\nValidated Setting -- Browser Simulation enabled");
// Check ACCEPT header Setting
if (string.IsNullOrEmpty(ConfigData.HeaderAccept.Value))
{
throw new ExceptionWithConfigData("Browser Simulation enabled, Please enter an ACCEPT header !", ConfigData);
}
else
{
output("Validated Setting -- ACCEPT (header) => " + ConfigData.HeaderAccept.Value.ToString());
}
// Check ACCEPT-LANG header Setting
if (string.IsNullOrEmpty(ConfigData.HeaderAcceptLang.Value))
{
throw new ExceptionWithConfigData("Browser Simulation enabled, Please enter an ACCEPT-LANG header !", ConfigData);
}
else
{
output("Validated Setting -- ACCEPT-LANG (header) => " + ConfigData.HeaderAcceptLang.Value.ToString());
}
// Check USER-AGENT header Setting
if (string.IsNullOrEmpty(ConfigData.HeaderUserAgent.Value))
{
throw new ExceptionWithConfigData("Browser Simulation enabled, Please enter an USER-AGENT header !", ConfigData);
}
else
{
output("Validated Setting -- USER-AGENT (header) => " + ConfigData.HeaderUserAgent.Value.ToString());
}
}
// Check Dev Cache Settings
if (ConfigData.HardDriveCache.Value == true)
{
output("\nValidated Setting -- DEV Hard Drive Cache enabled");
// Check if Dev Mode enabled !
if (!ConfigData.DevMode.Value)
{
throw new ExceptionWithConfigData("Hard Drive is enabled but not in DEV MODE, Please enable DEV MODE !", ConfigData);
}
// Check Cache Keep Time Setting
if (!string.IsNullOrEmpty(ConfigData.HardDriveCacheKeepTime.Value))
{
try
{
output("Validated Setting -- Cache Keep Time (ms) => " + Convert.ToInt32(ConfigData.HardDriveCacheKeepTime.Value));
}
catch (Exception)
{
throw new ExceptionWithConfigData("Please enter a numeric hard drive keep time in ms !", ConfigData);
}
}
else
{
throw new ExceptionWithConfigData("Hard Drive Cache enabled, Please enter a maximum keep time for cache !", ConfigData);
}
}
else
{
// Delete cache if previously existed
cleanCacheStorage(true);
}
}
}
}

View File

@@ -56,13 +56,18 @@ namespace Jackett.Indexers
AddCategoryMapping(23, TorznabCatType.Audio);
}
new ConfigurationDataBasicLogin configData
{
get { return (ConfigurationDataBasicLogin)base.configData; }
set { base.configData = value; }
}
public async Task<IndexerConfigurationStatus> ApplyConfiguration(JToken configJson)
{
var incomingConfig = new ConfigurationDataBasicLogin();
incomingConfig.LoadValuesFromJson(configJson);
configData.LoadValuesFromJson(configJson);
var pairs = new Dictionary<string, string> {
{ "username", incomingConfig.Username.Value },
{ "password", incomingConfig.Password.Value },
{ "username", configData.Username.Value },
{ "password", configData.Password.Value },
{ "login", "Login" },
{ "keeplogged", "1" }
};
@@ -74,7 +79,7 @@ namespace Jackett.Indexers
CQ dom = response.Content;
dom["#loginform > table"].Remove();
var errorMessage = dom["#loginform"].Text().Trim().Replace("\n\t", " ");
throw new ExceptionWithConfigData(errorMessage, (ConfigurationData)incomingConfig);
throw new ExceptionWithConfigData(errorMessage, configData);
});
return IndexerConfigurationStatus.RequiresTesting;
}

View File

@@ -24,9 +24,17 @@ namespace Jackett.Indexers
{
public class AnimeBytes : BaseIndexer, IIndexer
{
enum SearchType
{
Video,
Audio
}
private string LoginUrl { get { return SiteLink + "user/login"; } }
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; } }
new ConfigurationDataAnimeBytes configData
{
@@ -45,7 +53,10 @@ namespace Jackett.Indexers
TorznabCatType.BooksComics,
TorznabCatType.ConsolePSP,
TorznabCatType.ConsoleOther,
TorznabCatType.PCGames),
TorznabCatType.PCGames,
TorznabCatType.AudioMP3,
TorznabCatType.AudioLossless,
TorznabCatType.AudioOther),
logger: l,
p: ps,
configData: new ConfigurationDataAnimeBytes())
@@ -53,6 +64,13 @@ namespace Jackett.Indexers
}
public IEnumerable<ReleaseInfo> FilterResults(TorznabQuery query, IEnumerable<ReleaseInfo> input)
{
// Prevent filtering
return input;
}
public async Task<IndexerConfigurationStatus> ApplyConfiguration(JToken configJson)
{
configData.LoadValuesFromJson(configJson);
@@ -119,12 +137,28 @@ namespace Jackett.Indexers
base.LoadFromSavedConfiguration(jsonConfig);
}
private string StripEpisodeNumber(string term)
{
// Tracer does not support searching with episode number so strip it if we have one
term = Regex.Replace(term, @"\W(\dx)?\d?\d$", string.Empty);
term = Regex.Replace(term, @"\W(S\d\d?E)?\d?\d$", string.Empty);
return term;
}
public 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))
{
releases.Add(result);
}
}
foreach (var result in await GetResults(query.SanitizedSearchTerm))
foreach (var result in await GetResults(SearchType.Video, StripEpisodeNumber(query.SanitizedSearchTerm)))
{
releases.Add(result);
}
@@ -132,39 +166,45 @@ namespace Jackett.Indexers
return releases.ToArray();
}
public async Task<IEnumerable<ReleaseInfo>> GetResults(string searchTerm)
private bool ContainsMusicCategories(int[] categories)
{
var music = new[]
{
TorznabCatType.Audio.ID,
TorznabCatType.AudioMP3.ID,
TorznabCatType.AudioLossless.ID,
TorznabCatType.AudioOther.ID,
TorznabCatType.AudioForeign.ID
};
return categories.Length == 0 || music.Any(categories.Contains);
}
private async Task<IEnumerable<ReleaseInfo>> GetResults(SearchType searchType, string searchTerm)
{
var cleanSearchTerm = HttpUtility.UrlEncode(searchTerm);
// This tracker only deals with full seasons so chop off the episode/season number if we have it D:
if (!string.IsNullOrWhiteSpace(searchTerm))
{
var splitindex = searchTerm.LastIndexOf(' ');
if (splitindex > -1)
searchTerm = searchTerm.Substring(0, splitindex);
}
// The result list
var releases = new List<ReleaseInfo>();
var queryUrl = searchType == SearchType.Video ? SearchUrl : MusicSearchUrl;
// Only include the query bit if its required as hopefully the site caches the non query page
if (!string.IsNullOrWhiteSpace(searchTerm))
{
queryUrl += string.Format("searchstr={0}&action=advanced&search_type=title&year=&year2=&tags=&tags_type=0&sort=time_added&way=desc&hentai=2&releasegroup=&epcount=&epcount2=&artbooktitle=", cleanSearchTerm);
}
// Check cache first so we don't query the server for each episode when searching for each episode in a series.
lock (cache)
{
// Remove old cache items
CleanCache();
var cachedResult = cache.Where(i => i.Query == searchTerm).FirstOrDefault();
var cachedResult = cache.Where(i => i.Query == queryUrl).FirstOrDefault();
if (cachedResult != null)
return cachedResult.Results.Select(s => (ReleaseInfo)s.Clone()).ToArray();
}
var queryUrl = SearchUrl;
// Only include the query bit if its required as hopefully the site caches the non query page
if (!string.IsNullOrWhiteSpace(searchTerm))
{
queryUrl += string.Format("searchstr={0}&action=advanced&search_type=title&year=&year2=&tags=&tags_type=0&sort=time_added&way=desc&hentai=2&releasegroup=&epcount=&epcount2=&artbooktitle=", cleanSearchTerm);
}
// Get the content from the tracker
var response = await RequestStringWithCookiesAndRetry(queryUrl);
CQ dom = response.Content;
@@ -183,7 +223,11 @@ namespace Jackett.Indexers
var seriesCq = series.Cq();
var synonyms = new List<string>();
var mainTitle = seriesCq.Find(".group_title strong a").First().Text().Trim();
string mainTitle;
if (searchType == SearchType.Video)
mainTitle = seriesCq.Find(".group_title strong a").First().Text().Trim();
else
mainTitle = seriesCq.Find(".group_title strong").Text().Trim();
var yearStr = seriesCq.Find(".group_title strong").First().Text().Trim().Replace("]", "").Trim();
int yearIndex = yearStr.LastIndexOf("[");
@@ -244,6 +288,12 @@ namespace Jackett.Indexers
releaseInfo = releaseInfo.Replace("Episode ", "");
releaseInfo = releaseInfo.Replace("Season ", "S");
releaseInfo = releaseInfo.Trim();
int test = 0;
if (InsertSeason && int.TryParse(releaseInfo, out test) && releaseInfo.Length==1)
{
releaseInfo = "S01E0" + releaseInfo;
}
}
else if (rowCq.HasClass("torrent"))
{
@@ -268,33 +318,52 @@ namespace Jackett.Indexers
release.Guid = new Uri(SiteLink + infoLink.Attributes.GetAttribute("href") + "&nh=" + StringUtil.Hash(title)); // Sonarr should dedupe on this url - allow a url per name.
release.Link = new Uri(downloadLink.Attributes.GetAttribute("href"), UriKind.Relative);
var category = seriesCq.Find("a[title=\"View Torrent\"]").Text().Trim();
if (category == "TV Series")
release.Category = TorznabCatType.TVAnime.ID;
// Ignore these categories as they'll cause hell with the matcher
// TV Special, OVA, ONA, DVD Special, BD Special
if (category == "Movie")
release.Category = TorznabCatType.Movies.ID;
if (category == "Manga" || category == "Oneshot" || category == "Anthology" || category == "Manhwa" || category == "Manhua" || category == "Light Novel")
release.Category = TorznabCatType.BooksComics.ID;
if (category == "Novel" || category == "Artbook")
release.Category = TorznabCatType.BooksComics.ID;
if (category == "Game" || category == "Visual Novel")
string category = null;
if (searchType == SearchType.Video)
{
var description = rowCq.Find(".torrent_properties a:eq(1)").Text();
if (description.Contains(" PSP "))
release.Category = TorznabCatType.ConsolePSP.ID;
if (description.Contains("PSX"))
release.Category = TorznabCatType.ConsoleOther.ID;
if (description.Contains(" NES "))
release.Category = TorznabCatType.ConsoleOther.ID;
if (description.Contains(" PC "))
release.Category = TorznabCatType.PCGames.ID;
category = seriesCq.Find("a[title=\"View Torrent\"]").Text().Trim();
if (category == "TV Series")
release.Category = TorznabCatType.TVAnime.ID;
// Ignore these categories as they'll cause hell with the matcher
// TV Special, OVA, ONA, DVD Special, BD Special
if (category == "Movie")
release.Category = TorznabCatType.Movies.ID;
if (category == "Manga" || category == "Oneshot" || category == "Anthology" || category == "Manhwa" || category == "Manhua" || category == "Light Novel")
release.Category = TorznabCatType.BooksComics.ID;
if (category == "Novel" || category == "Artbook")
release.Category = TorznabCatType.BooksComics.ID;
if (category == "Game" || category == "Visual Novel")
{
var description = rowCq.Find(".torrent_properties a:eq(1)").Text();
if (description.Contains(" PSP "))
release.Category = TorznabCatType.ConsolePSP.ID;
if (description.Contains("PSX"))
release.Category = TorznabCatType.ConsoleOther.ID;
if (description.Contains(" NES "))
release.Category = TorznabCatType.ConsoleOther.ID;
if (description.Contains(" PC "))
release.Category = TorznabCatType.PCGames.ID;
}
}
if (searchType == SearchType.Audio)
{
category = seriesCq.Find(".group_img .cat a").Text();
if (category == "Single" || category == "Album" || category == "Compilation" || category == "Soundtrack" || category == "Remix CD")
{
var description = rowCq.Find(".torrent_properties a:eq(1)").Text();
if (description.Contains(" Lossless "))
release.Category = TorznabCatType.AudioLossless.ID;
else if (description.Contains("MP3"))
release.Category = TorznabCatType.AudioMP3.ID;
else
release.Category = TorznabCatType.AudioOther.ID;
}
}
@@ -372,7 +441,7 @@ namespace Jackett.Indexers
// Add to the cache
lock (cache)
{
cache.Add(new CachedQueryResult(searchTerm, releases));
cache.Add(new CachedQueryResult(queryUrl, releases));
}
return releases.Select(s => (ReleaseInfo)s.Clone());

View File

@@ -141,43 +141,75 @@ namespace Jackett.Indexers
protected void CleanCache()
{
foreach (var expired in cache.Where(i => i.Created - DateTime.Now > cacheTime).ToList())
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)
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);
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)
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);
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 async Task DoFollowIfRedirect(WebClientByteResult incomingResponse, string referrer = null, string overrideRedirectUrl = null, string overrideCookies = null)
private String ResolveCookies(String incomingCookies = "")
{
var redirRequestCookies = (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());
}
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 = overrideCookies ?? CookieHeader
Cookies = redirRequestCookies
});
Mapper.Map(redirectedResponse, incomingResponse);
}
@@ -233,7 +265,7 @@ namespace Jackett.Indexers
public async virtual Task<byte[]> Download(Uri link)
{
var response = await RequestBytesWithCookiesAndRetry(link.ToString());
if(response.Status != System.Net.HttpStatusCode.OK && response.Status != System.Net.HttpStatusCode.Continue && response.Status != System.Net.HttpStatusCode.PartialContent)
if (response.Status != System.Net.HttpStatusCode.OK && response.Status != System.Net.HttpStatusCode.Continue && response.Status != System.Net.HttpStatusCode.PartialContent)
{
throw new Exception($"Remote server returned {response.Status.ToString()}");
}
@@ -269,7 +301,7 @@ namespace Jackett.Indexers
Type = RequestType.GET,
Cookies = CookieHeader,
Referer = referer,
Headers = headers
Headers = headers
};
if (cookieOverride != null)
@@ -277,7 +309,7 @@ namespace Jackett.Indexers
return await webclient.GetString(request);
}
protected async Task<WebClientStringResult> RequestStringWithCookiesAndRetry(string url, string cookieOverride = null, string referer = null, Dictionary<string,string> headers = null)
protected async Task<WebClientStringResult> RequestStringWithCookiesAndRetry(string url, string cookieOverride = null, string referer = null, Dictionary<string, string> headers = null)
{
Exception lastException = null;
for (int i = 0; i < 3; i++)
@@ -349,7 +381,7 @@ namespace Jackett.Indexers
throw lastException;
}
protected async Task<WebClientStringResult> RequestLoginAndFollowRedirect(string url, IEnumerable<KeyValuePair<string, string>> data, string cookies, bool returnCookiesFromFirstCall, string redirectUrlOverride = null, string referer = null)
protected async Task<WebClientStringResult> RequestLoginAndFollowRedirect(string url, IEnumerable<KeyValuePair<string, string>> data, string cookies, bool returnCookiesFromFirstCall, string redirectUrlOverride = null, string referer = null, bool accumulateCookies = false)
{
var request = new Utils.Clients.WebRequest()
{
@@ -360,18 +392,22 @@ namespace Jackett.Indexers
PostData = data
};
var response = await webclient.GetString(request);
if (accumulateCookies)
{
response.Cookies = ResolveCookies((request.Cookies == null ? "" : request.Cookies + " ") + response.Cookies);
}
var firstCallCookies = response.Cookies;
if (response.IsRedirect)
{
await FollowIfRedirect(response, request.Url, redirectUrlOverride, response.Cookies);
await FollowIfRedirect(response, request.Url, redirectUrlOverride, response.Cookies, accumulateCookies);
}
if (returnCookiesFromFirstCall)
{
response.Cookies = firstCallCookies;
response.Cookies = ResolveCookies(firstCallCookies + (accumulateCookies ? " " + response.Cookies : ""));
}
return response;
}
@@ -400,43 +436,23 @@ namespace Jackett.Indexers
}
}
protected void AddCategoryMapping(string trackerCategory, int newznabCategory)
protected void AddCategoryMapping(string trackerCategory, TorznabCategory newznabCategory)
{
categoryMapping.Add(new CategoryMapping(trackerCategory, newznabCategory));
categoryMapping.Add(new CategoryMapping(trackerCategory, newznabCategory.ID));
if (!TorznabCaps.Categories.Contains(newznabCategory))
TorznabCaps.Categories.Add(newznabCategory);
}
protected void AddCategoryMapping(int trackerCategory, TorznabCategory newznabCategory)
{
categoryMapping.Add(new CategoryMapping(trackerCategory.ToString(), newznabCategory.ID));
if (!TorznabCaps.Categories.Contains(newznabCategory))
TorznabCaps.Categories.Add(newznabCategory);
}
protected void AddCategoryMapping(string trackerCategory, TorznabCategory newznabCategory)
{
categoryMapping.Add(new CategoryMapping(trackerCategory.ToString(), newznabCategory.ID));
if (!TorznabCaps.Categories.Contains(newznabCategory))
TorznabCaps.Categories.Add(newznabCategory);
}
protected void AddCategoryMapping(int trackerCategory, int newznabCategory)
{
categoryMapping.Add(new CategoryMapping(trackerCategory.ToString(), newznabCategory));
AddCategoryMapping(trackerCategory.ToString(), newznabCategory);
}
protected void AddMultiCategoryMapping(TorznabCategory newznabCategory, params int[] trackerCategories)
{
foreach (var trackerCat in trackerCategories)
{
categoryMapping.Add(new CategoryMapping(trackerCat.ToString(), newznabCategory.ID));
}
}
protected void AddMultiCategoryMapping(int trackerCategory, params TorznabCategory[] newznabCategories)
{
foreach (var newznabCat in newznabCategories)
{
categoryMapping.Add(new CategoryMapping(trackerCategory.ToString(), newznabCat.ID));
AddCategoryMapping(trackerCat, newznabCategory);
}
}

View File

@@ -30,7 +30,7 @@ namespace Jackett.Indexers
public BeyondHD(IIndexerManagerService i, Logger l, IWebClient w, IProtectionService ps)
: base(name: "BeyondHD",
description: "Without BeyondHD, your HDTV is just a TV",
link: "https://beyondhd.me/",
link: "https://beyond-hd.me/",
caps: new TorznabCapabilities(),
manager: i,
client: w,
@@ -79,7 +79,7 @@ namespace Jackett.Indexers
public override async Task<ConfigurationData> GetConfigurationForSetup()
{
var loginPage = await RequestStringWithCookies(LoginUrl, string.Empty);
string recaptchaSiteKey = new Regex(@"loginwidget', \{[\s]{4,30}'sitekey' : '([0-9A-Za-z]{5,60})',[\s]{4,30}'theme'").Match(loginPage.Content).Groups[1].ToString().Trim();
string recaptchaSiteKey = new Regex(@"loginwidget', \{[\s]{4,30}'sitekey' : '([0-9A-Za-z-]{5,60})',[\s]{4,30}'theme'").Match(loginPage.Content).Groups[1].ToString().Trim();
var result = new ConfigurationDataRecaptchaLogin();
result.CookieHeader.Value = loginPage.Cookies;
result.Captcha.SiteKey = recaptchaSiteKey;

View File

@@ -23,7 +23,7 @@ namespace Jackett.Indexers
{
private string LoginUrl { get { return SiteLink + "takelogin.php"; } }
private string SearchUrl { get { return SiteLink + "torrents.php?"; } }
private string DownloadUrl { get { return SiteLink + "download.php?/{0}/dl.torrent"; } }
private string DownloadUrl { get { return SiteLink + "download.php?id={0}"; } }
new ConfigurationDataBasicLogin configData
{

View File

@@ -42,7 +42,7 @@ namespace Jackett.Indexers
client: c,
logger: l,
p: ps,
configData: new ConfigurationDataCaptchaLogin())
configData: new ConfigurationDataCaptchaLogin("Ensure that you have the 'Force SSL' option set to 'yes' in your profile on the BitMeTv webpage."))
{
}

View File

@@ -0,0 +1,247 @@
using CsQuery;
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.Globalization;
using System.Linq;
using System.Net;
using System.Net.Http;
using System.Text;
using System.Threading.Tasks;
using System.Web;
using Jackett.Models.IndexerConfig;
using System.Collections.Specialized;
using System.Text.RegularExpressions;
namespace Jackett.Indexers
{
public class BitSoup : BaseIndexer, IIndexer
{
private string UseLink { get { return (this.configData.AlternateLink.Value != null && this.configData.AlternateLink.Value != "" ? this.configData.AlternateLink.Value : SiteLink); } }
private string BrowseUrl { get { return UseLink + "browse.php"; } }
private string LoginUrl { get { return UseLink + "takelogin.php"; } }
private string LoginReferer { get { return UseLink + "login.php"; } }
private List<String> KnownURLs = new List<String> { "https://www.bitsoup.me/", "https://www.bitsoup.org/" };
new ConfigurationDataBasicLoginWithAlternateLink configData
{
get { return (ConfigurationDataBasicLoginWithAlternateLink)base.configData; }
set { base.configData = value; }
}
public BitSoup(IIndexerManagerService i, IWebClient wc, Logger l, IProtectionService ps)
: base(name: "BitSoup",
description: "SoupieBits",
link: "https://www.bitsoup.me/",
caps: TorznabUtil.CreateDefaultTorznabTVCaps(),
manager: i,
client: wc,
logger: l,
p: ps,
configData: new ConfigurationDataBasicLoginWithAlternateLink())
{
this.configData.Instructions.Value = this.DisplayName + " has multiple URLs. The default (" + this.SiteLink + ") can be changed by entering a new value in the box below.";
this.configData.Instructions.Value += "The following are some known URLs for " + this.DisplayName;
this.configData.Instructions.Value += "<ul><li>" + String.Join("</li><li>", this.KnownURLs.ToArray()) + "</li></ul>";
//AddCategoryMapping("624", TorznabCatType.Console);
//AddCategoryMapping("307", TorznabCatType.ConsoleNDS);
//AddCategoryMapping("308", TorznabCatType.ConsolePSP);
AddCategoryMapping("35", TorznabCatType.ConsoleWii);
//AddCategoryMapping("309", TorznabCatType.ConsoleXbox);
AddCategoryMapping("12", TorznabCatType.ConsoleXbox360);
//AddCategoryMapping("305", TorznabCatType.ConsoleWiiwareVC);
//AddCategoryMapping("309", TorznabCatType.ConsoleXBOX360DLC);
AddCategoryMapping("38", TorznabCatType.ConsolePS3);
//AddCategoryMapping("239", TorznabCatType.ConsoleOther);
//AddCategoryMapping("245", TorznabCatType.ConsoleOther);
//AddCategoryMapping("246", TorznabCatType.ConsoleOther);
//AddCategoryMapping("626", TorznabCatType.ConsoleOther);
//AddCategoryMapping("628", TorznabCatType.ConsoleOther);
//AddCategoryMapping("630", TorznabCatType.ConsoleOther);
//AddCategoryMapping("307", TorznabCatType.Console3DS);
//AddCategoryMapping("308", TorznabCatType.ConsolePSVita);
//AddCategoryMapping("307", TorznabCatType.ConsoleWiiU);
//AddCategoryMapping("309", TorznabCatType.ConsoleXboxOne);
//AddCategoryMapping("308", TorznabCatType.ConsolePS4);
//AddCategoryMapping("631", TorznabCatType.Movies);
//AddCategoryMapping("631", TorznabCatType.MoviesForeign);
//AddCategoryMapping("455", TorznabCatType.MoviesOther);
//AddCategoryMapping("633", TorznabCatType.MoviesOther);
AddCategoryMapping("19", TorznabCatType.MoviesSD);
AddCategoryMapping("41", TorznabCatType.MoviesHD);
AddCategoryMapping("17", TorznabCatType.Movies3D);
AddCategoryMapping("80", TorznabCatType.MoviesBluRay);
AddCategoryMapping("20", TorznabCatType.MoviesDVD);
//AddCategoryMapping("631", TorznabCatType.MoviesWEBDL);
AddCategoryMapping("6", TorznabCatType.Audio);
//AddCategoryMapping("623", TorznabCatType.AudioMP3);
AddCategoryMapping("29", TorznabCatType.AudioVideo);
//AddCategoryMapping("402", TorznabCatType.AudioVideo);
AddCategoryMapping("5", TorznabCatType.AudioAudiobook);
//AddCategoryMapping("1", TorznabCatType.AudioLossless);
//AddCategoryMapping("403", TorznabCatType.AudioOther);
//AddCategoryMapping("642", TorznabCatType.AudioOther);
//AddCategoryMapping("1", TorznabCatType.AudioForeign);
//AddCategoryMapping("233", TorznabCatType.PC);
//AddCategoryMapping("236", TorznabCatType.PC);
//AddCategoryMapping("1", TorznabCatType.PC0day);
AddCategoryMapping("1", TorznabCatType.PCISO);
//AddCategoryMapping("235", TorznabCatType.PCMac);
//AddCategoryMapping("627", TorznabCatType.PCPhoneOther);
AddCategoryMapping("21", TorznabCatType.PCGames);
AddCategoryMapping("4", TorznabCatType.PCGames);
//AddCategoryMapping("625", TorznabCatType.PCPhoneIOS);
//AddCategoryMapping("625", TorznabCatType.PCPhoneAndroid);
AddCategoryMapping("45", TorznabCatType.TV);
//AddCategoryMapping("433", TorznabCatType.TV);
//AddCategoryMapping("639", TorznabCatType.TVWEBDL);
//AddCategoryMapping("433", TorznabCatType.TVWEBDL);
//AddCategoryMapping("639", TorznabCatType.TVFOREIGN);
//AddCategoryMapping("433", TorznabCatType.TVFOREIGN);
AddCategoryMapping("7", TorznabCatType.TVSD);
AddCategoryMapping("49", TorznabCatType.TVSD);
AddCategoryMapping("42", TorznabCatType.TVHD);
//AddCategoryMapping("433", TorznabCatType.TVHD);
//AddCategoryMapping("635", TorznabCatType.TVOTHER);
//AddCategoryMapping("636", TorznabCatType.TVSport);
AddCategoryMapping("23", TorznabCatType.TVAnime);
//AddCategoryMapping("634", TorznabCatType.TVDocumentary);
AddCategoryMapping("9", TorznabCatType.XXX);
//AddCategoryMapping("1", TorznabCatType.XXXDVD);
//AddCategoryMapping("1", TorznabCatType.XXXWMV);
//AddCategoryMapping("1", TorznabCatType.XXXXviD);
//AddCategoryMapping("1", TorznabCatType.XXXx264);
//AddCategoryMapping("1", TorznabCatType.XXXOther);
//AddCategoryMapping("1", TorznabCatType.XXXImageset);
//AddCategoryMapping("1", TorznabCatType.XXXPacks);
//AddCategoryMapping("340", TorznabCatType.Other);
//AddCategoryMapping("342", TorznabCatType.Other);
//AddCategoryMapping("344", TorznabCatType.Other);
//AddCategoryMapping("391", TorznabCatType.Other);
//AddCategoryMapping("392", TorznabCatType.Other);
//AddCategoryMapping("393", TorznabCatType.Other);
//AddCategoryMapping("394", TorznabCatType.Other);
//AddCategoryMapping("234", TorznabCatType.Other);
//AddCategoryMapping("638", TorznabCatType.Other);
//AddCategoryMapping("629", TorznabCatType.Other);
//AddCategoryMapping("1", TorznabCatType.OtherMisc);
//AddCategoryMapping("1", TorznabCatType.OtherHashed);
//AddCategoryMapping("408", TorznabCatType.Books);
AddCategoryMapping("24", TorznabCatType.BooksEbook);
//AddCategoryMapping("406", TorznabCatType.BooksComics);
//AddCategoryMapping("407", TorznabCatType.BooksComics);
//AddCategoryMapping("409", TorznabCatType.BooksComics);
//AddCategoryMapping("410", TorznabCatType.BooksMagazines);
//AddCategoryMapping("1", TorznabCatType.BooksTechnical);
//AddCategoryMapping("1", TorznabCatType.BooksOther);
//AddCategoryMapping("1", TorznabCatType.BooksForeign);
}
public async Task<IndexerConfigurationStatus> ApplyConfiguration(JToken configJson)
{
configData.LoadValuesFromJson(configJson);
if (configData.AlternateLink.Value != null && configData.AlternateLink.Value != "")
{
if (!configData.AlternateLink.Value.EndsWith("/"))
{
configData.AlternateLink.Value = null;
throw new Exception("AlternateLink must end with a slash.");
}
var match = Regex.Match(configData.AlternateLink.Value, "^https?:\\/\\/(?:[\\w]+\\.)+(?:[a-zA-Z]+)\\/$");
if (!match.Success)
{
configData.AlternateLink.Value = null;
throw new Exception("AlternateLink must be a valid url.");
}
}
var pairs = new Dictionary<string, string> {
{ "username", configData.Username.Value },
{ "password", configData.Password.Value },
};
var loginPage = await RequestStringWithCookies(UseLink, string.Empty);
var result = await RequestLoginAndFollowRedirect(LoginUrl, pairs, loginPage.Cookies, true, null, LoginReferer, true);
await ConfigureIfOK(result.Cookies, result.Content != null && result.Content.Contains("logout.php"), () =>
{
CQ dom = result.Content;
var messageEl = dom["body > table.statusbar1 > tbody > tr > td > table > tbody > tr > td > table > tbody > tr > td"].First();
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>();
var searchString = query.GetQueryString();
var searchUrl = BrowseUrl;
var trackerCats = MapTorznabCapsToTrackers(query);
var queryCollection = new NameValueCollection();
queryCollection.Add("search", string.IsNullOrWhiteSpace(searchString) ? "" : searchString);
if (trackerCats.Count > 1)
{
for (var ct = 0; ct < trackerCats.Count; ct++) queryCollection.Add("cat" + (ct + 1), trackerCats.ElementAt(ct));
}
else
{
queryCollection.Add("cat", (trackerCats.Count == 1 ? trackerCats.ElementAt(0) : "0"));
}
//queryCollection.Add("cat", (trackerCats.Count == 1 ? trackerCats.ElementAt(0) : "0"));
searchUrl += "?" + queryCollection.GetQueryString();
await ProcessPage(releases, searchUrl);
return releases;
}
private async Task ProcessPage(List<ReleaseInfo> releases, string searchUrl)
{
var response = await RequestStringWithCookiesAndRetry(searchUrl, null, BrowseUrl);
var results = response.Content;
try
{
CQ dom = results;
var rows = dom["table.koptekst tr"];
foreach (var row in rows.Skip(1))
{
var release = new ReleaseInfo();
release.Title = row.Cq().Find("td:eq(1) a").First().Text().Trim();
release.Comments = new Uri(UseLink + row.Cq().Find("td:eq(1) a").First().Attr("href"));
release.Link = new Uri(UseLink + row.Cq().Find("td:eq(2) a").First().Attr("href"));
release.Guid = release.Link;
release.Description = release.Title;
var cat = row.Cq().Find("td:eq(0) a").First().Attr("href").Substring(15);
release.Category = MapTrackerCatToNewznab(cat);
var added = row.Cq().Find("td:eq(7)").First().Text().Trim();
release.PublishDate = DateTime.ParseExact(added, "yyyy-MM-ddH:mm:ss", CultureInfo.InvariantCulture);
var sizeStr = row.Cq().Find("td:eq(8)").First().Text().Trim();
release.Size = ReleaseInfo.GetBytes(sizeStr);
release.Seeders = ParseUtil.CoerceInt(row.Cq().Find("td:eq(10)").First().Text().Trim());
release.Peers = ParseUtil.CoerceInt(row.Cq().Find("td:eq(11)").First().Text().Trim()) + release.Seeders;
releases.Add(release);
}
}
catch (Exception ex)
{
OnParseError(results, ex);
}
}
}
}

View File

@@ -78,6 +78,7 @@ namespace Jackett.Indexers
AddCategoryMapping("25", TorznabCatType.MoviesOther);
AddCategoryMapping("21", TorznabCatType.MoviesOther);
AddCategoryMapping("20", TorznabCatType.MoviesDVD);
AddCategoryMapping("26", TorznabCatType.MoviesWEBDL);
AddCategoryMapping("9", TorznabCatType.TVAnime);
AddCategoryMapping("34", TorznabCatType.Other);
AddCategoryMapping("35", TorznabCatType.Audio);
@@ -254,4 +255,4 @@ namespace Jackett.Indexers
}
}
}

View File

@@ -1,8 +1,6 @@
using CsQuery;
using Jackett.Indexers;
using Jackett.Models;
using Jackett.Services;
using Jackett.Utils;
using Jackett.Utils.Clients;
using Newtonsoft.Json.Linq;
using NLog;
@@ -10,14 +8,9 @@ using System;
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
using System.Net;
using System.Net.Http;
using System.Net.Http.Headers;
using System.Text;
using System.Text.RegularExpressions;
using System.Threading.Tasks;
using System.Web;
using System.Web.UI.WebControls;
using CsQuery.ExtensionMethods;
using Jackett.Models.IndexerConfig;
@@ -38,7 +31,7 @@ namespace Jackett.Indexers
: base(name: "DanishBits",
description: "A danish closed torrent tracker",
link: "https://danishbits.org/",
caps: TorznabUtil.CreateDefaultTorznabTVCaps(),
caps: new TorznabCapabilities(),
manager: i,
client: c,
logger: l,
@@ -151,6 +144,13 @@ namespace Jackett.Indexers
public 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);
TimeSpan delta = new TimeSpan(1, 0, 0);
TimeZoneInfo.AdjustmentRule adjustment = TimeZoneInfo.AdjustmentRule.CreateAdjustmentRule(new DateTime(1999, 10, 1), DateTime.MaxValue.Date, delta, startTransition, endTransition);
TimeZoneInfo.AdjustmentRule[] adjustments = { adjustment };
TimeZoneInfo denmarkTz = TimeZoneInfo.CreateCustomTimeZone("Denmark Time", new TimeSpan(1, 0, 0), "(GMT+01:00) Denmark Time", "Denmark Time", "Denmark DST", adjustments);
var releasesPerPage = 100;
var releases = new List<ReleaseInfo>();
@@ -244,9 +244,8 @@ namespace Jackett.Indexers
var addedElement = qRow.Find("span.time").FirstElement();
var addedStr = addedElement.GetAttribute("title");
release.PublishDate = DateTime.ParseExact(addedStr, "MMM dd yyyy, HH:mm",
CultureInfo.InvariantCulture);
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

@@ -19,7 +19,7 @@ namespace Jackett.Indexers
public class Demonoid : BaseIndexer, IIndexer
{
private string LoginUrl { get { return SiteLink + "account_handler.php"; } }
private string SearchUrl { get { return SiteLink + "files/?category=3&subcategory=All&quality=All&seeded=0&to=1&query={0}"; } }
private string SearchUrl { get { return SiteLink + "files/?category={0}&subcategory=All&quality=All&seeded=0&to=1&query={1}"; } }
new ConfigurationDataBasicLogin configData
{
@@ -38,6 +38,10 @@ namespace Jackett.Indexers
p: ps,
configData: new ConfigurationDataBasicLogin())
{
AddCategoryMapping(3, TorznabCatType.TV);
AddCategoryMapping(3, TorznabCatType.TVSD);
AddCategoryMapping(3, TorznabCatType.TVHD);
AddCategoryMapping(1, TorznabCatType.Movies);
}
public async Task<IndexerConfigurationStatus> ApplyConfiguration(JToken configJson)
@@ -64,9 +68,12 @@ namespace Jackett.Indexers
public async Task<IEnumerable<ReleaseInfo>> PerformQuery(TorznabQuery query)
{
var releases = new List<ReleaseInfo>();
var episodeSearchUrl = string.Format(SearchUrl, HttpUtility.UrlEncode(query.GetQueryString()));
var trackerCats = MapTorznabCapsToTrackers(query);
var cat = (trackerCats.Count == 1 ? trackerCats.ElementAt(0) : "0");
var episodeSearchUrl = string.Format(SearchUrl, cat, HttpUtility.UrlEncode(query.GetQueryString()));
var results = await RequestStringWithCookiesAndRetry(episodeSearchUrl);
if (results.Content.Contains("No torrents found"))
{
return releases;

View File

@@ -0,0 +1,975 @@
using System;
using System.Collections.Generic;
using System.Collections.Specialized;
using System.Globalization;
using System.Linq;
using System.Reflection;
using System.Text.RegularExpressions;
using System.Threading.Tasks;
using CsQuery;
using Jackett.Models;
using Jackett.Models.IndexerConfig.Bespoke;
using Jackett.Services;
using Jackett.Utils;
using Jackett.Utils.Clients;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using NLog;
namespace Jackett.Indexers
{
/// <summary>
/// Provider for French-ADN Private Tracker
/// </summary>
public class FrenchAdn : BaseIndexer, IIndexer
{
private string LoginUrl => SiteLink + "login.php?";
private string LoginCheckUrl => SiteLink + "takelogin.php";
private string SearchUrl => SiteLink + "browse.php";
private string TorrentCommentUrl => SiteLink + "details.php?id={id}#comments";
private string TorrentDescriptionUrl => SiteLink + "details.php?id={id}";
private string TorrentDownloadUrl => SiteLink + "download.php?id={id}";
private string TorrentThanksUrl => SiteLink + "takethanks.php";
private bool Latency => ConfigData.Latency.Value;
private bool DevMode => ConfigData.DevMode.Value;
private bool CacheMode => ConfigData.HardDriveCache.Value;
private static string Directory => System.IO.Path.GetTempPath() + "Jackett\\" + MethodBase.GetCurrentMethod().DeclaringType?.Name + "\\";
private readonly Dictionary<string, string> _emulatedBrowserHeaders = new Dictionary<string, string>();
private CQ _fDom;
private ConfigurationDataFrenchAdn ConfigData => (ConfigurationDataFrenchAdn)configData;
public FrenchAdn(IIndexerManagerService i, IWebClient w, Logger l, IProtectionService ps)
: base(
name: "French-ADN",
description: "Your French Family Provider",
link: "https://french-adn.com/",
caps: new TorznabCapabilities(),
manager: i,
client: w,
logger: l,
p: ps,
downloadBase: "https://french-adn.com/download.php?id=",
configData: new ConfigurationDataFrenchAdn())
{
// Clean capabilities
TorznabCaps.Categories.Clear();
// Movies
AddCategoryMapping("15", TorznabCatType.Movies); // ALL
AddCategoryMapping("108", TorznabCatType.MoviesSD); // TS CAM
AddCategoryMapping("25", TorznabCatType.MoviesSD); // BDRIP
AddCategoryMapping("56", TorznabCatType.MoviesSD); // BRRIP
AddCategoryMapping("16", TorznabCatType.MoviesSD); // DVDRIP
AddCategoryMapping("49", TorznabCatType.MoviesDVD); // TVRIP
AddCategoryMapping("102", TorznabCatType.MoviesWEBDL); // WEBRIP
AddCategoryMapping("105", TorznabCatType.MoviesHD); // 1080P
AddCategoryMapping("104", TorznabCatType.MoviesHD); // 720P
AddCategoryMapping("17", TorznabCatType.MoviesDVD); // DVD R
AddCategoryMapping("21", TorznabCatType.MoviesDVD); // DVD R5
AddCategoryMapping("112", TorznabCatType.MoviesDVD); // DVD REMUX
AddCategoryMapping("107", TorznabCatType.Movies3D); // 3D
AddCategoryMapping("113", TorznabCatType.MoviesBluRay); // BLURAY
AddCategoryMapping("118", TorznabCatType.MoviesHD); // MHD
// Series
AddCategoryMapping("41", TorznabCatType.TV); // ALL
AddCategoryMapping("43", TorznabCatType.TV); // VF
AddCategoryMapping("44", TorznabCatType.TV); // VOSTFR
AddCategoryMapping("42", TorznabCatType.TV); // PACK
// TV
AddCategoryMapping("110", TorznabCatType.TV); // SHOWS
// Anime
AddCategoryMapping("109", TorznabCatType.TVAnime); // ANIME
// Manga
AddCategoryMapping("119", TorznabCatType.TVAnime); // MANGA
// Documentaries
AddCategoryMapping("114", TorznabCatType.TVDocumentary); // DOCUMENTARY
// Music
AddCategoryMapping("22", TorznabCatType.Audio); // ALL
AddCategoryMapping("24", TorznabCatType.AudioLossless); // FLAC
AddCategoryMapping("23", TorznabCatType.AudioMP3); // MP3
// Games
AddCategoryMapping("33", TorznabCatType.PCGames); // ALL
AddCategoryMapping("45", TorznabCatType.PCGames); // PC GAMES
AddCategoryMapping("93", TorznabCatType.Console3DS); // 3DS
AddCategoryMapping("94", TorznabCatType.Console); // PS2
AddCategoryMapping("93", TorznabCatType.ConsolePS3); // PS3
AddCategoryMapping("95", TorznabCatType.ConsolePSP); // PSP
AddCategoryMapping("35", TorznabCatType.ConsolePS3); // WII
// Applications
AddCategoryMapping("11", TorznabCatType.PC); // ALL
AddCategoryMapping("12", TorznabCatType.PC); // APPS WINDOWS
AddCategoryMapping("97", TorznabCatType.PCMac); // APPS MAC
AddCategoryMapping("98", TorznabCatType.PC); // APPS LINUX
// Books
AddCategoryMapping("115", TorznabCatType.BooksEbook); // EBOOK
AddCategoryMapping("114", TorznabCatType.BooksComics); // COMICS
// Other
AddCategoryMapping("103", TorznabCatType.Other); // STAFF
}
/// <summary>
/// Configure our FADN Provider
/// </summary>
/// <param name="configJson">Our params in Json</param>
/// <returns>Configuration state</returns>
public async Task<IndexerConfigurationStatus> ApplyConfiguration(JToken configJson)
{
// Retrieve config values set by Jackett's user
ConfigData.LoadValuesFromJson(configJson);
// Check & Validate Config
ValidateConfig();
// Setting our data for a better emulated browser (maximum security)
// TODO: Encoded Content not supported by Jackett at this time
// emulatedBrowserHeaders.Add("Accept-Encoding", "gzip, deflate");
// If we want to simulate a browser
if (ConfigData.Browser.Value)
{
// Clean headers
_emulatedBrowserHeaders.Clear();
// Inject headers
_emulatedBrowserHeaders.Add("Accept", ConfigData.HeaderAccept.Value);
_emulatedBrowserHeaders.Add("Accept-Language", ConfigData.HeaderAcceptLang.Value);
_emulatedBrowserHeaders.Add("DNT", Convert.ToInt32(ConfigData.HeaderDnt.Value).ToString());
_emulatedBrowserHeaders.Add("Upgrade-Insecure-Requests", Convert.ToInt32(ConfigData.HeaderUpgradeInsecure.Value).ToString());
_emulatedBrowserHeaders.Add("User-Agent", ConfigData.HeaderUserAgent.Value);
}
await DoLogin();
return IndexerConfigurationStatus.RequiresTesting;
}
/// <summary>
/// Perform login to racker
/// </summary>
/// <returns></returns>
private async Task DoLogin()
{
// Build WebRequest for index
var myIndexRequest = new WebRequest()
{
Type = RequestType.GET,
Url = SiteLink,
Headers = _emulatedBrowserHeaders
};
// Get index page for cookies
Output("\nGetting index page (for cookies).. with " + SiteLink);
var indexPage = await webclient.GetString(myIndexRequest);
// Building login form data
var pairs = new Dictionary<string, string> {
{ "username", ConfigData.Username.Value },
{ "password", ConfigData.Password.Value }
};
// Build WebRequest for login
var myRequestLogin = new WebRequest()
{
Type = RequestType.GET,
Url = LoginUrl,
Headers = _emulatedBrowserHeaders,
Cookies = indexPage.Cookies,
Referer = SiteLink
};
// Get login page -- (not used, but simulation needed by tracker security's checks)
LatencyNow();
Output("\nGetting login page (user simulation).. with " + LoginUrl);
await webclient.GetString(myRequestLogin);
// Build WebRequest for submitting authentification
var request = new WebRequest()
{
PostData = pairs,
Referer = LoginUrl,
Type = RequestType.POST,
Url = LoginCheckUrl,
Headers = _emulatedBrowserHeaders,
Cookies = indexPage.Cookies,
};
// Perform loggin
LatencyNow();
Output("\nPerform loggin.. with " + LoginCheckUrl);
var response = await webclient.GetString(request);
// Test if we are logged in
await ConfigureIfOK(response.Cookies, !string.IsNullOrEmpty(response.Cookies) && !response.IsRedirect, () =>
{
// Default error message
var message = "Error during attempt !";
// Parse redirect header
var redirectTo = response.RedirectingTo;
// Analyzer error code
if (redirectTo.Contains("login.php?error=4"))
{
// Set message
message = "Wrong username or password !";
}
// Oops, unable to login
Output("-> Login failed: " + message, "error");
throw new ExceptionWithConfigData("Login failed: " + message, configData);
});
Output("\nCookies saved for future uses...");
ConfigData.CookieHeader.Value = indexPage.Cookies + " " + response.Cookies + " ts_username=" + ConfigData.Username.Value;
Output("\n-> Login Success\n");
}
/// <summary>
/// Check logged-in state for provider
/// </summary>
/// <returns></returns>
private async Task CheckLogin()
{
// Checking ...
Output("\n-> Checking logged-in state....");
var loggedInCheck = await RequestStringWithCookies(SearchUrl);
if (!loggedInCheck.Content.Contains("/logout.php"))
{
// Cookie expired, renew session on provider
Output("-> Not logged, login now...\n");
await DoLogin();
}
else
{
// Already logged, session active
Output("-> Already logged, continue...\n");
}
}
/// <summary>
/// Execute our search query
/// </summary>
/// <param name="query">Query</param>
/// <returns>Releases</returns>
public async Task<IEnumerable<ReleaseInfo>> PerformQuery(TorznabQuery query)
{
var releases = new List<ReleaseInfo>();
var torrentRowList = new List<CQ>();
var searchTerm = query.GetQueryString();
var searchUrl = SearchUrl;
// Check login before performing a query
await CheckLogin();
// Check cache first so we don't query the server (if search term used or not in dev mode)
if (!DevMode && !string.IsNullOrEmpty(searchTerm))
{
lock (cache)
{
// Remove old cache items
CleanCache();
// Search in cache
var cachedResult = cache.FirstOrDefault(i => i.Query == searchTerm);
if (cachedResult != null)
return cachedResult.Results.Select(s => (ReleaseInfo)s.Clone()).ToArray();
}
}
// Build our query
var request = BuildQuery(searchTerm, query, searchUrl);
// Getting results & Store content
var results = await QueryExec(request);
_fDom = results.Content;
try
{
// Find torrent rows
var firstPageRows = FindTorrentRows();
// Add them to torrents list
torrentRowList.AddRange(firstPageRows.Select(fRow => fRow.Cq()));
// Check if there are pagination links at bottom
var pagination = (_fDom["#quicknavpage_menu"].Length != 0);
// If pagination available
int nbResults;
int pageLinkCount;
if (pagination)
{
// Retrieve available pages (3 pages shown max)
pageLinkCount = _fDom["#navcontainer_f:first > ul"].Find("a").Not(".smalltext").Not("#quicknavpage").Length;
// Last button ? (So more than 3 page are available)
var more = _fDom["#navcontainer_f:first > ul"].Find("a.smalltext").Length > 1;
// More page than 3 pages ?
if (more)
{
// Get total page count from last link
pageLinkCount = ParseUtil.CoerceInt(Regex.Match(_fDom["#navcontainer_f:first > ul"].Find("a:eq(4)").Attr("href"), @"\d+").Value);
}
// Calculate average number of results (based on torrents rows lenght on first page)
nbResults = firstPageRows.Count() * pageLinkCount;
}
else {
nbResults = 1;
pageLinkCount = 1;
// Check if we have a minimum of one result
if (firstPageRows.Length > 1)
{
// Retrieve total count on our alone page
nbResults = firstPageRows.Count();
}
else
{
// Check if no result
if(torrentRowList.First().Find("td").Length == 1)
{
// No results found
Output("\nNo result found for your query, please try another search term ...\n", "info");
// No result found for this query
return releases;
}
}
}
Output("\nFound " + nbResults + " result(s) (+/- " + firstPageRows.Length + ") in " + pageLinkCount + " page(s) for this query !");
Output("\nThere are " + firstPageRows.Length + " results on the first page !");
// If we have a term used for search and pagination result superior to one
if (!string.IsNullOrWhiteSpace(query.GetQueryString()) && pageLinkCount > 1)
{
// Starting with page #2
for (var i = 2; i <= Math.Min(int.Parse(ConfigData.Pages.Value), pageLinkCount); i++)
{
Output("\nProcessing page #" + i);
// Request our page
LatencyNow();
// Build our query -- Minus 1 to page due to strange pagination number on tracker side, starting with page 0...
var pageRequest = BuildQuery(searchTerm, query, searchUrl, i);
// Getting results & Store content
WebClientStringResult pageResults = await QueryExec(pageRequest);
// Assign response
_fDom = pageResults.Content;
// Process page results
var additionalPageRows = FindTorrentRows();
// Add them to torrents list
torrentRowList.AddRange(additionalPageRows.Select(fRow => fRow.Cq()));
}
}
// Loop on results
foreach (var tRow in torrentRowList)
{
Output("\n=>> Torrent #" + (releases.Count + 1));
// ID
var id = ParseUtil.CoerceInt(Regex.Match(tRow.Find("td:eq(1) > div:first > a").Attr("name"), @"\d+").Value);
Output("ID: " + id);
// Release Name -- Can be truncated ... Need FIX on tracker's side !
var name = tRow.Find("td:eq(1) > div > a:eq(2)").Text().Trim();
Output("Release: " + name);
// Category
var categoryId = ParseUtil.CoerceInt(Regex.Match(tRow.Find("td:eq(0) > a").Attr("href"), @"\d+").Value);
var categoryName = tRow.Find("td:eq(0) > a > img").Attr("title").Split(new[] { ':' }, 2)[1].Trim();
Output("Category: " + MapTrackerCatToNewznab(categoryId.ToString()) + " (" + categoryId + " - " + categoryName + ")");
// Seeders
var seeders = ParseUtil.CoerceInt(Regex.Match(tRow.Find("td:eq(4) > div > font > a").Text(), @"\d+").Value);
Output("Seeders: " + seeders);
// Leechers
var leechers = ParseUtil.CoerceInt(Regex.Match(tRow.Find("td:eq(5) > div > font").Text(), @"\d+").Value);
Output("Leechers: " + leechers);
// Files
var files = ParseUtil.CoerceInt(Regex.Match(tRow.Find("td:eq(2)").Text(), @"\d+").Value);
Output("Files: " + files);
// Comments
var comments = ParseUtil.CoerceInt(Regex.Match(tRow.Find("td:eq(3)").Text(), @"\d+").Value);
Output("Comments: " + files);
// Health
var percent = ParseUtil.CoerceInt(Regex.Match(tRow.Find("td:eq(6) > img").Attr("src"), @"\d+").Value) * 10;
Output("Health: " + percent + "%");
// Size
var humanSize = tRow.Find("td:eq(7)").Text().ToLowerInvariant();
var size = ReleaseInfo.GetBytes(humanSize);
Output("Size: " + humanSize + " (" + size + " bytes)");
// Date & Genre
var infosData = tRow.Find("td:eq(1) > div:last").Text();
var infosList = Regex.Split(infosData, "\\|").ToList();
var infosTorrent = infosList.Select(s => s.Split(new[] { ':' }, 2)[1].Trim()).ToList();
// --> Date
var date = FormatDate(infosTorrent.First());
Output("Released on: " + date.ToLocalTime());
// --> Genre
var genre = infosTorrent.Last();
Output("Genre: " + genre);
// Torrent Details URL
var detailsLink = new Uri(TorrentDescriptionUrl.Replace("{id}", id.ToString()));
Output("Details: " + detailsLink.AbsoluteUri);
// Torrent Comments URL
var commentsLink = new Uri(TorrentCommentUrl.Replace("{id}", id.ToString()));
Output("Comments Link: " + commentsLink.AbsoluteUri);
// Torrent Download URL
var downloadLink = new Uri(TorrentDownloadUrl.Replace("{id}", id.ToString()));
Output("Download Link: " + downloadLink.AbsoluteUri);
// Building release infos
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
};
releases.Add(release);
}
}
catch (Exception ex)
{
OnParseError("Error, unable to parse result \n" + ex.StackTrace, ex);
}
// Return found releases
return releases;
}
/// <summary>
/// Build query to process
/// </summary>
/// <param name="term">Term to search</param>
/// <param name="query">Torznab Query for categories mapping</param>
/// <param name="url">Search url for provider</param>
/// <param name="page">Page number to request</param>
/// <returns>URL to query for parsing and processing results</returns>
private string BuildQuery(string term, TorznabQuery query, string url, int page = 0)
{
var parameters = new NameValueCollection();
var categoriesList = MapTorznabCapsToTrackers(query);
// Building our tracker query
parameters.Add("do", "search");
// If search term provided
if (!string.IsNullOrWhiteSpace(term))
{
// Add search term ~~ Strange search engine, need to replace space with dot for results !
parameters.Add("keywords", term.Replace(' ', '.'));
}
else
{
// Showing all torrents (just for output function)
parameters.Add("keywords", "");
term = "all";
}
// Adding requested categories
parameters.Add("category", categoriesList.Count > 0 ? string.Join(",", categoriesList) : "");
// Building our tracker query
parameters.Add("search_type", "t_name");
// Check if we are processing a new page
if (page > 1)
{
// Adding page number to query
parameters.Add("page", page.ToString());
}
// Building our query
url += "?" + parameters.GetQueryString();
Output("\nBuilded query for \"" + term + "\"... " + url);
// Return our search url
return url;
}
/// <summary>
/// Switch Method for Querying
/// </summary>
/// <param name="request">URL created by Query Builder</param>
/// <returns>Results from query</returns>
private async Task<WebClientStringResult> QueryExec(string request)
{
WebClientStringResult results;
// Switch in we are in DEV mode with Hard Drive Cache or not
if (DevMode && CacheMode)
{
// Check Cache before querying and load previous results if available
results = await QueryCache(request);
}
else
{
// Querying tracker directly
results = await QueryTracker(request);
}
return results;
}
/// <summary>
/// Get Torrents Page from Cache by Query Provided
/// </summary>
/// <param name="request">URL created by Query Builder</param>
/// <returns>Results from query</returns>
private async Task<WebClientStringResult> QueryCache(string request)
{
WebClientStringResult results;
// Create Directory if not exist
System.IO.Directory.CreateDirectory(Directory);
// Clean Storage Provider Directory from outdated cached queries
CleanCacheStorage();
// Create fingerprint for request
var file = Directory + request.GetHashCode() + ".json";
// Checking modes states
if (System.IO.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));
}
else
{
// No cached file found, querying tracker directly
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));
}
return results;
}
/// <summary>
/// Get Torrents Page from Tracker by Query Provided
/// </summary>
/// <param name="request">URL created by Query Builder</param>
/// <returns>Results from query</returns>
private async Task<WebClientStringResult> QueryTracker(string request)
{
// Cache mode not enabled or cached file didn't exist for our query
Output("\nQuerying tracker for results....");
// Request our first page
LatencyNow();
var results = await RequestStringWithCookiesAndRetry(request, ConfigData.CookieHeader.Value, SearchUrl, _emulatedBrowserHeaders);
// Return results from tracker
return results;
}
/// <summary>
/// Clean Hard Drive Cache Storage
/// </summary>
/// <param name="force">Force Provider Folder deletion</param>
private void CleanCacheStorage(bool force = false)
{
// Check cleaning method
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))
{
// Delete storage directory of provider
System.IO.Directory.Delete(Directory, true);
Output("-> Storage folder deleted successfully.");
}
else
{
// No directory, so nothing to do
Output("-> No Storage folder found for this provider !");
}
}
else
{
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))
.Where(f => f.LastAccessTime < DateTime.Now.AddMilliseconds(-Convert.ToInt32(ConfigData.HardDriveCacheKeepTime.Value)))
.ToList()
.ForEach(f => {
Output("Deleting cached file << " + f.Name + " >> ... done.");
f.Delete();
i++;
});
// Inform on what was cleaned during process
if (i > 0)
{
Output("-> Deleted " + i + " cached files during cleaning.");
}
else {
Output("-> Nothing deleted during cleaning.");
}
}
}
/// <summary>
/// Generate a random fake latency to avoid detection on tracker side
/// </summary>
private void LatencyNow()
{
// Need latency ?
if (Latency)
{
var random = new Random(DateTime.Now.Millisecond);
var waiting = random.Next(Convert.ToInt32(ConfigData.LatencyStart.Value),
Convert.ToInt32(ConfigData.LatencyEnd.Value));
Output("\nLatency Faker => Sleeping for " + waiting + " ms...");
// Sleep now...
System.Threading.Thread.Sleep(waiting);
}
// Generate a random value in our range
}
/// <summary>
/// Find torrent rows in search pages
/// </summary>
/// <returns>JQuery Object</returns>
private CQ FindTorrentRows()
{
// Return all occurencis of torrents found
return _fDom["#showcontents > table > tbody > tr:not(:first)"];
}
/// <summary>
/// Format Date to DateTime
/// </summary>
/// <param name="clock"></param>
/// <returns>A DateTime</returns>
private static DateTime FormatDate(string clock)
{
DateTime date;
// Switch from date format
if(clock.Contains("Aujourd'hui") || clock.Contains("Hier"))
{
// Get hours & minutes
IList<int> infosClock = clock.Split(':').Select(s => ParseUtil.CoerceInt(Regex.Match(s, @"\d+").Value)).ToList();
// Ago date with today
date = new DateTime(DateTime.Now.Year, DateTime.Now.Month, DateTime.Now.Day, Convert.ToInt32(infosClock[0]), Convert.ToInt32(infosClock[1]), DateTime.Now.Second);
// Set yesterday if necessary
if (clock.Contains("Hier"))
{
// Remove one day from date
// ReSharper disable once ReturnValueOfPureMethodIsNotUsed
date.AddDays(-1);
}
}
else
{
// Parse Date if full
date = DateTime.ParseExact(clock, "MM-dd-yyyy HH:mm", CultureInfo.GetCultureInfo("fr-FR"), DateTimeStyles.AssumeLocal);
}
return date.ToUniversalTime();
}
/// <summary>
/// Download torrent file from tracker
/// </summary>
/// <param name="link">URL string</param>
/// <returns></returns>
public override async Task<byte[]> Download(Uri link)
{
// This tracker need to thanks Uploader before getting torrent file...
Output("\nThis tracker needs you to thank uploader before downloading torrent!");
// Retrieving ID from link provided
var id = ParseUtil.CoerceInt(Regex.Match(link.AbsoluteUri, @"\d+").Value);
Output("Torrent Requested ID: " + id);
// Building login form data
var pairs = new Dictionary<string, string> {
{ "torrentid", id.ToString() },
{ "_", string.Empty } // ~~ Strange, blank param...
};
// Add emulated XHR request
_emulatedBrowserHeaders.Add("X-Prototype-Version", "1.6.0.3");
_emulatedBrowserHeaders.Add("X-Requested-With", "XMLHttpRequest");
// Build WebRequest for thanks
var myRequestThanks = new WebRequest()
{
Type = RequestType.POST,
PostData = pairs,
Url = TorrentThanksUrl,
Headers = _emulatedBrowserHeaders,
Cookies = ConfigData.CookieHeader.Value,
Referer = TorrentDescriptionUrl.Replace("{id}", id.ToString())
};
// Get thanks page -- (not used, just for doing a request)
LatencyNow();
Output("Thanks user, to get download link for our torrent.. with " + TorrentThanksUrl);
await webclient.GetString(myRequestThanks);
// Get torrent file now
Output("Getting torrent file now....");
var response = await base.Download(link);
// Remove our XHR request header
_emulatedBrowserHeaders.Remove("X-Prototype-Version");
_emulatedBrowserHeaders.Remove("X-Requested-With");
// Return content
return response;
}
/// <summary>
/// Output message for logging or developpment (console)
/// </summary>
/// <param name="message">Message to output</param>
/// <param name="level">Level for Logger</param>
private void Output(string message, string level = "debug")
{
// Check if we are in dev mode
if (DevMode)
{
// Output message to console
Console.WriteLine(message);
}
else
{
// Send message to logger with level
switch (level)
{
default:
goto case "debug";
case "debug":
// Only if Debug Level Enabled on Jackett
if (Engine.Logger.IsDebugEnabled)
{
logger.Debug(message);
}
break;
case "info":
logger.Info(message);
break;
case "error":
logger.Error(message);
break;
}
}
}
/// <summary>
/// Validate Config entered by user on Jackett
/// </summary>
private void ValidateConfig()
{
Output("\nValidating Settings ... \n");
// Check Username Setting
if (string.IsNullOrEmpty(ConfigData.Username.Value))
{
throw new ExceptionWithConfigData("You must provide a username for this tracker to login !", ConfigData);
}
else
{
Output("Validated Setting -- Username (auth) => " + ConfigData.Username.Value);
}
// Check Password Setting
if (string.IsNullOrEmpty(ConfigData.Password.Value))
{
throw new ExceptionWithConfigData("You must provide a password with your username for this tracker to login !", ConfigData);
}
else
{
Output("Validated Setting -- Password (auth) => " + ConfigData.Password.Value);
}
// Check Max Page Setting
if (!string.IsNullOrEmpty(ConfigData.Pages.Value))
{
try
{
Output("Validated Setting -- Max Pages => " + Convert.ToInt32(ConfigData.Pages.Value));
}
catch (Exception)
{
throw new ExceptionWithConfigData("Please enter a numeric maximum number of pages to crawl !", ConfigData);
}
}
else
{
throw new ExceptionWithConfigData("Please enter a maximum number of pages to crawl !", ConfigData);
}
// Check Latency Setting
if (ConfigData.Latency.Value)
{
Output("\nValidated Setting -- Latency Simulation enabled");
// Check Latency Start Setting
if (!string.IsNullOrEmpty(ConfigData.LatencyStart.Value))
{
try
{
Output("Validated Setting -- Latency Start => " + Convert.ToInt32(ConfigData.LatencyStart.Value));
}
catch (Exception)
{
throw new ExceptionWithConfigData("Please enter a numeric latency start in ms !", ConfigData);
}
}
else
{
throw new ExceptionWithConfigData("Latency Simulation enabled, Please enter a start latency !", ConfigData);
}
// Check Latency End Setting
if (!string.IsNullOrEmpty(ConfigData.LatencyEnd.Value))
{
try
{
Output("Validated Setting -- Latency End => " + Convert.ToInt32(ConfigData.LatencyEnd.Value));
}
catch (Exception)
{
throw new ExceptionWithConfigData("Please enter a numeric latency end in ms !", ConfigData);
}
}
else
{
throw new ExceptionWithConfigData("Latency Simulation enabled, Please enter a end latency !", ConfigData);
}
}
// Check Browser Setting
if (ConfigData.Browser.Value)
{
Output("\nValidated Setting -- Browser Simulation enabled");
// Check ACCEPT header Setting
if (string.IsNullOrEmpty(ConfigData.HeaderAccept.Value))
{
throw new ExceptionWithConfigData("Browser Simulation enabled, Please enter an ACCEPT header !", ConfigData);
}
else
{
Output("Validated Setting -- ACCEPT (header) => " + ConfigData.HeaderAccept.Value);
}
// Check ACCEPT-LANG header Setting
if (string.IsNullOrEmpty(ConfigData.HeaderAcceptLang.Value))
{
throw new ExceptionWithConfigData("Browser Simulation enabled, Please enter an ACCEPT-LANG header !", ConfigData);
}
else
{
Output("Validated Setting -- ACCEPT-LANG (header) => " + ConfigData.HeaderAcceptLang.Value);
}
// Check USER-AGENT header Setting
if (string.IsNullOrEmpty(ConfigData.HeaderUserAgent.Value))
{
throw new ExceptionWithConfigData("Browser Simulation enabled, Please enter an USER-AGENT header !", ConfigData);
}
else
{
Output("Validated Setting -- USER-AGENT (header) => " + ConfigData.HeaderUserAgent.Value);
}
}
else
{
// Browser simulation must be enabled (otherwhise, this provider will not work due to tracker's security)
throw new ExceptionWithConfigData("Browser Simulation must be enabled for this provider to work, please enable it !", ConfigData);
}
// Check Dev Cache Settings
if (ConfigData.HardDriveCache.Value)
{
Output("\nValidated Setting -- DEV Hard Drive Cache enabled");
// Check if Dev Mode enabled !
if (!ConfigData.DevMode.Value)
{
throw new ExceptionWithConfigData("Hard Drive is enabled but not in DEV MODE, Please enable DEV MODE !", ConfigData);
}
// Check Cache Keep Time Setting
if (!string.IsNullOrEmpty(ConfigData.HardDriveCacheKeepTime.Value))
{
try
{
Output("Validated Setting -- Cache Keep Time (ms) => " + Convert.ToInt32(ConfigData.HardDriveCacheKeepTime.Value));
}
catch (Exception)
{
throw new ExceptionWithConfigData("Please enter a numeric hard drive keep time in ms !", ConfigData);
}
}
else
{
throw new ExceptionWithConfigData("Hard Drive Cache enabled, Please enter a maximum keep time for cache !", ConfigData);
}
}
else
{
// Delete cache if previously existed
CleanCacheStorage(true);
}
}
}
}

View File

@@ -105,9 +105,9 @@ namespace Jackett.Indexers
release.MinimumSeedTime = 172800;
release.Title = qLink.Attr("title");
release.Description = release.Title;
release.Guid = new Uri(SiteLink + qLink.Attr("href"));
release.Guid = new Uri(SiteLink + qLink.Attr("href").TrimStart('/'));
release.Comments = release.Guid;
release.Link = new Uri(SiteLink + qRow.Find("td.table_links > a").First().Attr("href"));
release.Link = new Uri(SiteLink + qRow.Find("td.table_links > a").First().Attr("href").TrimStart('/'));
release.Category = TvCategoryParser.ParseTvShowQuality(release.Title);
release.Seeders = ParseUtil.CoerceInt(qRow.Find("td.table_seeders").Text().Trim());

View File

@@ -0,0 +1,295 @@
using CsQuery;
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.Globalization;
using System.Linq;
using System.Net;
using System.Net.Http;
using System.Text;
using System.Threading.Tasks;
using System.Web;
using Jackett.Models.IndexerConfig;
using System.Collections.Specialized;
using System.Threading;
namespace Jackett.Indexers
{
public class Fuzer : BaseIndexer, IIndexer
{
private string SearchUrl { get { return SiteLink + "index.php?name=torrents&"; } }
private string LoginUrl { get { return SiteLink + "login.php"; } }
private const int MAXPAGES = 3;
new ConfigurationDataBasicLogin configData
{
get { return (ConfigurationDataBasicLogin)base.configData; }
set { base.configData = value; }
}
public Fuzer(IIndexerManagerService i, Logger l, IWebClient w, IProtectionService ps)
: base(name: "Fuzer",
description: "Fuzer is a private torrent website with israeli torrents.",
link: "https://fuzer.me/",
manager: i,
client: w,
logger: l,
p: ps,
configData: new ConfigurationDataBasicLogin())
{
TorznabCaps.Categories.Clear();
AddMultiCategoryMapping(TorznabCatType.Movies, 7, 9, 58, 59, 60, 61, 83);
AddMultiCategoryMapping(TorznabCatType.MoviesSD, 7, 58);
AddMultiCategoryMapping(TorznabCatType.MoviesHD, 9, 59, 61);
AddMultiCategoryMapping(TorznabCatType.MoviesBluRay, 59);
AddMultiCategoryMapping(TorznabCatType.MoviesForeign, 83);
AddMultiCategoryMapping(TorznabCatType.MoviesDVD, 58);
AddMultiCategoryMapping(TorznabCatType.Movies3D, 9);
AddMultiCategoryMapping(TorznabCatType.MoviesWEBDL, 9);
AddMultiCategoryMapping(TorznabCatType.TV, 8, 10, 62, 63, 84);
AddMultiCategoryMapping(TorznabCatType.TVHD, 10, 63);
AddMultiCategoryMapping(TorznabCatType.TVFOREIGN, 62, 84);
AddMultiCategoryMapping(TorznabCatType.TVSport, 64);
AddMultiCategoryMapping(TorznabCatType.TVAnime, 65);
AddMultiCategoryMapping(TorznabCatType.TVWEBDL, 10, 63);
AddMultiCategoryMapping(TorznabCatType.TVSD, 8, 62, 84);
AddMultiCategoryMapping(TorznabCatType.TVDocumentary, 8, 10, 62, 63);
AddMultiCategoryMapping(TorznabCatType.Console, 12, 55, 56, 57);
AddMultiCategoryMapping(TorznabCatType.ConsoleXbox, 55);
AddMultiCategoryMapping(TorznabCatType.ConsoleXbox360, 55);
AddMultiCategoryMapping(TorznabCatType.ConsoleXBOX360DLC, 55);
AddMultiCategoryMapping(TorznabCatType.ConsolePS3, 12);
AddMultiCategoryMapping(TorznabCatType.ConsolePS4, 12);
AddMultiCategoryMapping(TorznabCatType.ConsoleXboxOne, 55);
AddMultiCategoryMapping(TorznabCatType.ConsolePS4, 12);
AddMultiCategoryMapping(TorznabCatType.ConsoleWii, 56);
AddMultiCategoryMapping(TorznabCatType.ConsoleWiiwareVC, 56);
AddMultiCategoryMapping(TorznabCatType.ConsolePSP, 57);
AddMultiCategoryMapping(TorznabCatType.ConsoleNDS, 57);
AddMultiCategoryMapping(TorznabCatType.MoviesOther, 57);
AddMultiCategoryMapping(TorznabCatType.PC, 11, 15);
AddMultiCategoryMapping(TorznabCatType.PCGames, 11);
AddMultiCategoryMapping(TorznabCatType.PCMac, 71);
AddMultiCategoryMapping(TorznabCatType.PCPhoneAndroid, 13);
AddMultiCategoryMapping(TorznabCatType.PCPhoneIOS, 70);
AddMultiCategoryMapping(TorznabCatType.Audio, 14, 66, 67, 68);
AddMultiCategoryMapping(TorznabCatType.AudioForeign, 14);
AddMultiCategoryMapping(TorznabCatType.AudioLossless, 67);
AddMultiCategoryMapping(TorznabCatType.AudioAudiobook, 69);
AddMultiCategoryMapping(TorznabCatType.AudioOther, 68);
AddMultiCategoryMapping(TorznabCatType.Other, 17);
AddMultiCategoryMapping(TorznabCatType.XXX, 16);
}
public async Task<IndexerConfigurationStatus> ApplyConfiguration(JToken configJson)
{
configData.LoadValuesFromJson(configJson);
var loginPage = await RequestStringWithCookies(LoginUrl, string.Empty);
var pairs = new Dictionary<string, string> {
{ "vb_login_username", configData.Username.Value },
{ "vb_login_password", "" },
{ "securitytoken", "guest" },
{ "do","login"},
{ "vb_login_md5password", StringUtil.Hash(configData.Password.Value).ToLower()},
{ "vb_login_md5password_utf", StringUtil.Hash(configData.Password.Value).ToLower()},
{ "cookieuser", "1" }
};
var result = await RequestLoginAndFollowRedirect(LoginUrl, pairs, loginPage.Cookies, true, null, LoginUrl);
await ConfigureIfOK(result.Cookies, result.Content != null && result.Content.Contains("images/loading.gif"), () =>
{
var errorMessage = "Couldn't login";
throw new ExceptionWithConfigData(errorMessage, configData);
});
Thread.Sleep(2);
return IndexerConfigurationStatus.RequiresTesting;
}
public async Task<IEnumerable<ReleaseInfo>> PerformQuery(TorznabQuery query)
{
var results = await performRegularQuery(query);
if (results.Count() == 0)
{
return await performHebrewQuery(query);
}
return results;
}
private async Task<IEnumerable<ReleaseInfo>> performHebrewQuery(TorznabQuery query)
{
var name = await getHebName(query.SearchTerm);
if (string.IsNullOrEmpty(name))
{
return new List<ReleaseInfo>();
}
else
{
return await performRegularQuery(query, name);
}
}
private async Task<IEnumerable<ReleaseInfo>> performRegularQuery(TorznabQuery query, string hebName = null)
{
var releases = new List<ReleaseInfo>();
var searchurls = new List<string>();
var searchUrl = SearchUrl;
var queryCollection = new NameValueCollection();
var searchString = query.GetQueryString();
if (hebName != null)
{
searchString = hebName + " - עונה " + query.Season + " פרק " + query.Episode;
}
int categoryCounter = 1;
foreach (var cat in MapTorznabCapsToTrackers(query))
{
searchUrl += "c" + categoryCounter.ToString() + "=" + cat + "&";
categoryCounter++;
}
if (string.IsNullOrWhiteSpace(searchString))
{
searchUrl = SiteLink + "index.php?name=torrents";
}
else
{
var strEncoded = HttpUtility.UrlEncode(searchString, Encoding.GetEncoding("Windows-1255"));
searchUrl += "text=" + strEncoded + "&category=0&search=1";
}
var data = await RequestBytesWithCookiesAndRetry(searchUrl);
var results = Encoding.GetEncoding("Windows-1255").GetString(data.Content);
try
{
CQ dom = results;
ReleaseInfo release;
int rowCount = 0;
var rows = dom["#collapseobj_module_17 > tr"];
foreach (var row in rows)
{
CQ qRow = row.Cq();
if (rowCount < 1 || qRow.Children().Count() != 9) //skip 1 row because there's an empty row
{
rowCount++;
continue;
}
release = new ReleaseInfo();
release.Description = qRow.Find("td:nth-child(2) > a").Text(); ;
if (hebName != null)
{
release.Title = query.SearchTerm + " " + release.Description.Substring(release.Description.IndexOf(string.Format("S{0:D2}E{1:D2}", query.Season, int.Parse(query.Episode))));
}
else
{
const string DELIMITER = " | ";
release.Title = release.Description.Substring(release.Description.IndexOf(DELIMITER) + DELIMITER.Length);
}
release.MinimumRatio = 1;
release.MinimumSeedTime = 172800;
int seeders, peers;
if (ParseUtil.TryCoerceInt(qRow.Find("td:nth-child(7) > div").Text(), out seeders))
{
release.Seeders = seeders;
if (ParseUtil.TryCoerceInt(qRow.Find("td:nth-child(8) > div").Text(), out peers))
{
release.Peers = peers + release.Seeders;
}
}
string fullSize = qRow.Find("td:nth-child(5) > div").Text();
release.Size = ReleaseInfo.GetBytes(fullSize);
release.Guid = new Uri(qRow.Find("td:nth-child(2) > a").Attr("href"));
release.Link = new Uri(SiteLink + qRow.Find("td:nth-child(3) > a").Attr("href"));
release.Comments = release.Guid;
string[] dateSplit = qRow.Find("td:nth-child(2) > span.torrentstime").Text().Split(' ');
string dateString = dateSplit[1] + " " + dateSplit[3];
release.PublishDate = DateTime.ParseExact(dateString, "dd-MM-yy HH:mm", CultureInfo.InvariantCulture);
string category = qRow.Find("script:nth-child(1)").Text();
int index = category.IndexOf("category=");
if (index == -1)
{
/// Other type
category = "17";
}
else
{
category = category.Substring(index + "category=".Length, 2);
if (category[1] == '\\')
{
category = category[0].ToString();
}
}
release.Category = MapTrackerCatToNewznab(category);
releases.Add(release);
}
}
catch (Exception ex)
{
OnParseError(results, ex);
}
return releases;
}
private async Task<string> getHebName(string searchTerm)
{
const string site = "http://thetvdb.com";
var url = site + "/index.php?searchseriesid=&tab=listseries&function=Search&";
url += "string=" + searchTerm; // eretz + nehedert
var results = await RequestStringWithCookies(url);
CQ dom = results.Content;
int rowCount = 0;
var rows = dom["#listtable > tbody > tr"];
foreach (var row in rows)
{
if (rowCount < 1)
{
rowCount++;
continue;
}
CQ qRow = row.Cq();
CQ link = qRow.Find("td:nth-child(1) > a");
if (link.Text().Trim().ToLower() == searchTerm.Trim().ToLower())
{
var address = link.Attr("href");
if (string.IsNullOrEmpty(address)) { continue; }
var realAddress = site + address.Replace("lid=7", "lid=24");
var realData = await RequestStringWithCookies(realAddress);
CQ realDom = realData.Content;
return realDom["#content:nth-child(1) > h1"].Text();
}
}
return string.Empty;
}
}
}

View File

@@ -0,0 +1,144 @@
using CsQuery;
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.Globalization;
using System.Threading.Tasks;
using System.Web;
using Jackett.Models.IndexerConfig;
using System.Text.RegularExpressions;
namespace Jackett.Indexers
{
public class Hebits : BaseIndexer, IIndexer
{
private string LoginUrl { get { return SiteLink + "login.php"; } }
private string LoginPostUrl { get { return SiteLink + "takeloginAjax.php"; } }
private string SearchUrl { get { return SiteLink + "browse.php?sort=4&type=desc"; } }
new ConfigurationDataBasicLogin configData
{
get { return (ConfigurationDataBasicLogin)base.configData; }
set { base.configData = value; }
}
public Hebits(IIndexerManagerService i, Logger l, IWebClient wc, IProtectionService ps)
: base(name: "Hebits",
description: "The Israeli Tracker",
link: "https://hebits.net/",
caps: TorznabUtil.CreateDefaultTorznabTVCaps(),
manager: i,
client: wc,
logger: l,
p: ps,
downloadBase: "https://hebits.net/",
configData: new ConfigurationDataBasicLogin())
{
AddCategoryMapping(19, TorznabCatType.MoviesSD);
AddCategoryMapping(25, TorznabCatType.MoviesOther); // Israeli Content
AddCategoryMapping(20, TorznabCatType.MoviesDVD);
AddCategoryMapping(36, TorznabCatType.MoviesBluRay);
AddCategoryMapping(27, TorznabCatType.MoviesHD);
AddCategoryMapping(7, TorznabCatType.TVSD); // Israeli SDTV
AddCategoryMapping(24, TorznabCatType.TVSD); // English SDTV
AddCategoryMapping(1, TorznabCatType.TVHD); // Israel HDTV
AddCategoryMapping(37, TorznabCatType.TVHD); // Israel HDTV
}
public async Task<IndexerConfigurationStatus> ApplyConfiguration(JToken configJson)
{
configData.LoadValuesFromJson(configJson);
var pairs = new Dictionary<string, string> {
{ "username", configData.Username.Value },
{ "password", configData.Password.Value }
};
// Get inital cookies
CookieHeader = string.Empty;
var result = await RequestLoginAndFollowRedirect(LoginPostUrl, pairs, CookieHeader, true, null, SiteLink);
await ConfigureIfOK(result.Cookies, result.Content != null && result.Content.Contains("OK"), () =>
{
CQ dom = result.Content;
var messageEl = dom["#errorMsg"].Last();
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>();
var searchString = query.GetQueryString();
var searchUrl = SearchUrl;
if (!string.IsNullOrWhiteSpace(searchString))
{
searchUrl += "&search=" + HttpUtility.UrlEncode(searchString);
}
string.Format(SearchUrl, HttpUtility.UrlEncode(searchString));
var cats = MapTorznabCapsToTrackers(query);
if (cats.Count > 0)
{
foreach (var cat in cats)
{
searchUrl += "&c" + cat + "=1";
}
}
var results = await RequestStringWithCookiesAndRetry(searchUrl);
try
{
CQ dom = results.Content;
CQ qRows = dom[".browse > div > div"];
foreach (var row in qRows)
{
var release = new ReleaseInfo();
var qRow = row.Cq();
var debug = qRow.Html();
release.MinimumRatio = 1;
release.MinimumSeedTime = 172800;
release.Title = qRow.Find(".bTitle").Text().Split('/')[1].Trim();
release.Link = new Uri(SiteLink + qRow.Find("a").Attr("href"));
release.Guid = release.Link;
var dateString = qRow.Find("div:last-child").Text().Trim();
var pattern = "\\d{4}-\\d{2}-\\d{2} \\d{2}:\\d{2}:\\d{2}";
var match = Regex.Match(dateString, pattern);
if (match.Success)
{
release.PublishDate = DateTime.ParseExact(match.Value, "yyyy-MM-dd HH:mm:ss", CultureInfo.InvariantCulture);
}
var sizeStr = qRow.Find(".bSize").Text();
release.Size = ReleaseInfo.GetBytes(sizeStr);
release.Seeders = ParseUtil.CoerceInt(qRow.Find(".bUping").Text().Trim());
release.Peers = release.Seeders + ParseUtil.CoerceInt(qRow.Find(".bDowning").Text().Trim());
releases.Add(release);
}
}
catch (Exception ex)
{
OnParseError(results.Content, ex);
}
return releases;
}
}
}

View File

@@ -0,0 +1,182 @@
using System;
using System.Collections.Generic;
using System.Collections.Specialized;
using System.Globalization;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using CsQuery;
using CsQuery.ExtensionMethods.Internal;
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
{
// ReSharper disable once InconsistentNaming
public class ILoveTorrents : BaseIndexer, IIndexer
{
private string BrowseUrl => SiteLink + "browse.php";
private string LoginUrl => SiteLink + "takelogin.php";
new ConfigurationDataBasicLogin configData
{
get { return (ConfigurationDataBasicLogin)base.configData; }
set { base.configData = value; }
}
public ILoveTorrents(IIndexerManagerService i, IWebClient wc, Logger l, IProtectionService ps)
: base(name: "ILoveTorrents",
description: "ILT",
link: "https://www.ilovetorrents.me/",
caps: new TorznabCapabilities(),
manager: i,
client: wc,
logger: l,
p: ps,
configData: new ConfigurationDataBasicLogin())
{
AddCategoryMapping(85, TorznabCatType.Movies3D);
AddCategoryMapping(23, TorznabCatType.TVAnime);
AddCategoryMapping(24, TorznabCatType.BooksEbook);
AddCategoryMapping(4, TorznabCatType.PCGames);
AddCategoryMapping(38, TorznabCatType.ConsolePS3);
AddCategoryMapping(38, TorznabCatType.ConsolePS4);
AddCategoryMapping(38, TorznabCatType.ConsolePSP);
AddCategoryMapping(43, TorznabCatType.ConsoleWii);
AddCategoryMapping(43, TorznabCatType.ConsoleWiiU);
AddCategoryMapping(12, TorznabCatType.ConsoleXBOX360DLC);
AddCategoryMapping(12, TorznabCatType.ConsoleXbox);
AddCategoryMapping(12, TorznabCatType.ConsoleXbox360);
AddCategoryMapping(12, TorznabCatType.ConsoleXboxOne);
AddCategoryMapping(6, TorznabCatType.Audio);
AddCategoryMapping(7, TorznabCatType.TV);
AddCategoryMapping(40, TorznabCatType.TVSD);
AddCategoryMapping(8, TorznabCatType.TVHD);
AddCategoryMapping(9, TorznabCatType.XXX);
AddCategoryMapping(11, TorznabCatType.XXXDVD);
AddCategoryMapping(10, TorznabCatType.XXXx264);
AddCategoryMapping(80, TorznabCatType.MoviesBluRay);
AddCategoryMapping(20, TorznabCatType.MoviesDVD);
AddCategoryMapping(41, TorznabCatType.MoviesHD);
AddCategoryMapping(19, TorznabCatType.Movies);
}
public async Task<IndexerConfigurationStatus> ApplyConfiguration(JToken configJson)
{
configData.LoadValuesFromJson(configJson);
var pairs = new Dictionary<string, string> {
{ "username", configData.Username.Value },
{ "password", configData.Password.Value },
{ "returnto", "/" },
{ "login", "Log in!" }
};
var loginPage = await RequestStringWithCookies(SiteLink, string.Empty);
var result = await RequestLoginAndFollowRedirect(LoginUrl, pairs, loginPage.Cookies, true, SiteLink, SiteLink);
await ConfigureIfOK(result.Cookies, result.Content != null && result.Content.Contains("logout.php"), () =>
{
CQ dom = result.Content;
var messageEl = dom["body > div"].First();
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>();
var searchString = query.GetQueryString();
var searchUrl = BrowseUrl;
var trackerCats = MapTorznabCapsToTrackers(query);
var queryCollection = new NameValueCollection();
// Tracker can only search OR return things in categories
if (!string.IsNullOrWhiteSpace(searchString))
{
queryCollection.Add("search", searchString);
queryCollection.Add("cat", "0");
}
else
{
foreach (var cat in MapTorznabCapsToTrackers(query))
{
queryCollection.Add("c" + cat, "1");
}
queryCollection.Add("incldead", "0");
}
searchUrl += "?" + queryCollection.GetQueryString();
await ProcessPage(releases, searchUrl);
return releases;
}
private async Task ProcessPage(List<ReleaseInfo> releases, string searchUrl)
{
var response = await RequestStringWithCookiesAndRetry(searchUrl, null, BrowseUrl);
var results = response.Content;
try
{
CQ dom = results;
var rows = dom[".koptekst tr"];
foreach (var row in rows.Skip(1))
{
var release = new ReleaseInfo();
var link = row.Cq().Find("td:eq(1) a:eq(0)").First();
release.Guid = new Uri(SiteLink + link.Attr("href"));
release.Comments = release.Guid;
release.Title = link.Text().Trim();
release.Description = release.Title;
// If we search an get no results, we still get a table just with no info.
if (string.IsNullOrWhiteSpace(release.Title))
{
break;
}
// Check if the release has been assigned a category
if (row.Cq().Find("td:eq(0) a").Length > 0)
{
var cat = row.Cq().Find("td:eq(0) a").First().Attr("href").Substring(15);
release.Category = MapTrackerCatToNewznab(cat);
}
var qLink = row.Cq().Find("td:eq(2) a").First();
release.Link = new Uri(SiteLink + qLink.Attr("href"));
var added = row.Cq().Find("td:eq(7)").First().Text().Trim();
var date = added.Substring(0, 10);
var time = added.Substring(12, 8);
var dateTime = date + time;
release.PublishDate = DateTime.ParseExact(dateTime, "yyyy-MM-ddHH:mm:ss", CultureInfo.InvariantCulture, DateTimeStyles.AssumeUniversal).ToLocalTime();
var sizeStr = row.Cq().Find("td:eq(8)").First().Text().Trim();
release.Size = ReleaseInfo.GetBytes(sizeStr);
release.Seeders = ParseUtil.CoerceInt(row.Cq().Find("td:eq(10)").First().Text().Trim());
release.Peers = ParseUtil.CoerceInt(row.Cq().Find("td:eq(11)").First().Text().Trim()) + release.Seeders;
releases.Add(release);
}
}
catch (Exception ex)
{
OnParseError(results, ex);
}
}
}
}

View File

@@ -21,11 +21,13 @@ namespace Jackett.Indexers
{
public class IPTorrents : BaseIndexer, IIndexer
{
private string BrowseUrl { get { return SiteLink + "t"; } }
private string UseLink { get { return (!String.IsNullOrEmpty(this.configData.AlternateLink.Value) ? this.configData.AlternateLink.Value : SiteLink); } }
private string BrowseUrl { get { return UseLink + "t"; } }
private List<String> KnownURLs = new List<String> { "https://nemo.iptorrents.com/", "https://ipt.rocks/" };
new ConfigurationDataBasicLogin configData
new ConfigurationDataBasicLoginWithAlternateLink configData
{
get { return (ConfigurationDataBasicLogin)base.configData; }
get { return (ConfigurationDataBasicLoginWithAlternateLink)base.configData; }
set { base.configData = value; }
}
@@ -38,8 +40,12 @@ namespace Jackett.Indexers
client: wc,
logger: l,
p: ps,
configData: new ConfigurationDataBasicLogin())
configData: new ConfigurationDataBasicLoginWithAlternateLink())
{
this.configData.Instructions.Value = this.DisplayName + " has multiple URLs. The default (" + this.SiteLink + ") can be changed by entering a new value in the box below.";
this.configData.Instructions.Value += "The following are some known URLs for " + this.DisplayName;
this.configData.Instructions.Value += "<ul><li>" + String.Join("</li><li>", this.KnownURLs.ToArray()) + "</li></ul>";
AddCategoryMapping(72, TorznabCatType.Movies);
AddCategoryMapping(77, TorznabCatType.MoviesSD);
AddCategoryMapping(89, TorznabCatType.MoviesSD);
@@ -92,9 +98,9 @@ namespace Jackett.Indexers
};
var request = new Utils.Clients.WebRequest()
{
Url = SiteLink,
Url = UseLink,
Type = RequestType.POST,
Referer = SiteLink,
Referer = UseLink,
PostData = pairs
};
var response = await webclient.GetString(request);
@@ -141,7 +147,7 @@ namespace Jackett.Indexers
{
CQ dom = results;
var rows = dom["table.torrents > tbody > tr"];
var rows = dom["table[id='torrents'] > tbody > tr"];
foreach (var row in rows.Skip(1))
{
var release = new ReleaseInfo();
@@ -156,7 +162,7 @@ namespace Jackett.Indexers
}
release.Description = release.Title;
release.Guid = new Uri(SiteLink + qTitleLink.Attr("href").Substring(1));
release.Guid = new Uri(UseLink + qTitleLink.Attr("href").Substring(1));
release.Comments = release.Guid;
var descString = qRow.Find(".t_ctime").Text();
@@ -165,7 +171,7 @@ namespace Jackett.Indexers
release.PublishDate = DateTimeUtil.FromTimeAgo(dateString);
var qLink = row.ChildElements.ElementAt(3).Cq().Children("a");
release.Link = new Uri(SiteLink + qLink.Attr("href").TrimStart('/'));
release.Link = new Uri(UseLink + HttpUtility.UrlEncode(qLink.Attr("href").TrimStart('/')));
var sizeStr = row.ChildElements.ElementAt(5).Cq().Text();
release.Size = ReleaseInfo.GetBytes(sizeStr);

View File

@@ -0,0 +1,242 @@
using CsQuery;
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.IO;
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;
using System.Collections.Specialized;
using System.Globalization;
namespace Jackett.Indexers
{
public class Myanonamouse : BaseIndexer, IIndexer
{
private string LoginUrl { get { return SiteLink + "takelogin.php"; } }
private string SearchUrl { get { return SiteLink + "tor/js/loadSearch2.php"; } }
new ConfigurationDataBasicLogin configData
{
get { return (ConfigurationDataBasicLogin)base.configData; }
set { base.configData = value; }
}
public Myanonamouse(IIndexerManagerService i, IWebClient c, Logger l, IProtectionService ps)
: base(name: "Myanonamouse",
description: "Friendliness, Warmth and Sharing",
link: "https://www.myanonamouse.net/",
caps: new TorznabCapabilities(TorznabCatType.Books,
TorznabCatType.AudioAudiobook,
TorznabCatType.BooksComics,
TorznabCatType.BooksEbook,
TorznabCatType.BooksMagazines,
TorznabCatType.BooksTechnical),
manager: i,
client: c,
logger: l,
p: ps,
configData: new ConfigurationDataBasicLogin())
{
AddCategoryMapping("61", TorznabCatType.BooksComics);
AddCategoryMapping("91", TorznabCatType.BooksTechnical);
AddCategoryMapping("80", TorznabCatType.BooksTechnical);
AddCategoryMapping("79", TorznabCatType.BooksMagazines);
AddCategoryMapping("39", TorznabCatType.AudioAudiobook);
AddCategoryMapping("49", TorznabCatType.AudioAudiobook);
AddCategoryMapping("50", TorznabCatType.AudioAudiobook);
AddCategoryMapping("83", TorznabCatType.AudioAudiobook);
AddCategoryMapping("51", TorznabCatType.AudioAudiobook);
AddCategoryMapping("97", TorznabCatType.AudioAudiobook);
AddCategoryMapping("40", TorznabCatType.AudioAudiobook);
AddCategoryMapping("41", TorznabCatType.AudioAudiobook);
AddCategoryMapping("106", TorznabCatType.AudioAudiobook);
AddCategoryMapping("42", TorznabCatType.AudioAudiobook);
AddCategoryMapping("52", TorznabCatType.AudioAudiobook);
AddCategoryMapping("98", TorznabCatType.AudioAudiobook);
AddCategoryMapping("54", TorznabCatType.AudioAudiobook);
AddCategoryMapping("55", TorznabCatType.AudioAudiobook);
AddCategoryMapping("43", TorznabCatType.AudioAudiobook);
AddCategoryMapping("99", TorznabCatType.AudioAudiobook);
AddCategoryMapping("84", TorznabCatType.AudioAudiobook);
AddCategoryMapping("44", TorznabCatType.AudioAudiobook);
AddCategoryMapping("56", TorznabCatType.AudioAudiobook);
AddCategoryMapping("137", TorznabCatType.AudioAudiobook);
AddCategoryMapping("45", TorznabCatType.AudioAudiobook);
AddCategoryMapping("57", TorznabCatType.AudioAudiobook);
AddCategoryMapping("85", TorznabCatType.AudioAudiobook);
AddCategoryMapping("87", TorznabCatType.AudioAudiobook);
AddCategoryMapping("119", TorznabCatType.AudioAudiobook);
AddCategoryMapping("88", TorznabCatType.AudioAudiobook);
AddCategoryMapping("58", TorznabCatType.AudioAudiobook);
AddCategoryMapping("59", TorznabCatType.AudioAudiobook);
AddCategoryMapping("46", TorznabCatType.AudioAudiobook);
AddCategoryMapping("47", TorznabCatType.AudioAudiobook);
AddCategoryMapping("53", TorznabCatType.AudioAudiobook);
AddCategoryMapping("89", TorznabCatType.AudioAudiobook);
AddCategoryMapping("100", TorznabCatType.AudioAudiobook);
AddCategoryMapping("108", TorznabCatType.AudioAudiobook);
AddCategoryMapping("48", TorznabCatType.AudioAudiobook);
AddCategoryMapping("111", TorznabCatType.AudioAudiobook);
AddCategoryMapping("60", TorznabCatType.BooksEbook);
AddCategoryMapping("71", TorznabCatType.BooksEbook);
AddCategoryMapping("72", TorznabCatType.BooksEbook);
AddCategoryMapping("90", TorznabCatType.BooksEbook);
AddCategoryMapping("73", TorznabCatType.BooksEbook);
AddCategoryMapping("101", TorznabCatType.BooksEbook);
AddCategoryMapping("62", TorznabCatType.BooksEbook);
AddCategoryMapping("63", TorznabCatType.BooksEbook);
AddCategoryMapping("107", TorznabCatType.BooksEbook);
AddCategoryMapping("64", TorznabCatType.BooksEbook);
AddCategoryMapping("74", TorznabCatType.BooksEbook);
AddCategoryMapping("102", TorznabCatType.BooksEbook);
AddCategoryMapping("76", TorznabCatType.BooksEbook);
AddCategoryMapping("77", TorznabCatType.BooksEbook);
AddCategoryMapping("65", TorznabCatType.BooksEbook);
AddCategoryMapping("103", TorznabCatType.BooksEbook);
AddCategoryMapping("115", TorznabCatType.BooksEbook);
AddCategoryMapping("66", TorznabCatType.BooksEbook);
AddCategoryMapping("78", TorznabCatType.BooksEbook);
AddCategoryMapping("138", TorznabCatType.BooksEbook);
AddCategoryMapping("67", TorznabCatType.BooksEbook);
AddCategoryMapping("92", TorznabCatType.BooksEbook);
AddCategoryMapping("118", TorznabCatType.BooksEbook);
AddCategoryMapping("94", TorznabCatType.BooksEbook);
AddCategoryMapping("120", TorznabCatType.BooksEbook);
AddCategoryMapping("95", TorznabCatType.BooksEbook);
AddCategoryMapping("81", TorznabCatType.BooksEbook);
AddCategoryMapping("82", TorznabCatType.BooksEbook);
AddCategoryMapping("68", TorznabCatType.BooksEbook);
AddCategoryMapping("69", TorznabCatType.BooksEbook);
AddCategoryMapping("75", TorznabCatType.BooksEbook);
AddCategoryMapping("96", TorznabCatType.BooksEbook);
AddCategoryMapping("104", TorznabCatType.BooksEbook);
AddCategoryMapping("109", TorznabCatType.BooksEbook);
AddCategoryMapping("70", TorznabCatType.BooksEbook);
AddCategoryMapping("112", TorznabCatType.BooksEbook);
}
public async Task<IndexerConfigurationStatus> ApplyConfiguration(JToken configJson)
{
configData.LoadValuesFromJson(configJson);
var pairs = new Dictionary<string, string> {
{ "email", configData.Username.Value },
{ "password", configData.Password.Value },
{ "returnto", "/" }
};
var preRequest = await RequestStringWithCookiesAndRetry(LoginUrl, string.Empty);
var result = await RequestLoginAndFollowRedirect(LoginUrl, pairs, preRequest.Cookies, true, SearchUrl, SiteLink);
await ConfigureIfOK(result.Cookies, result.Content != null && result.Content.Contains("Search Results"), () =>
{
CQ dom = result.Content;
var errorMessage = dom["table.main table td.text"].Text().Trim().Replace("\n\t", " ");
throw new ExceptionWithConfigData(errorMessage, configData);
});
return IndexerConfigurationStatus.RequiresTesting;
}
public async Task<IEnumerable<ReleaseInfo>> PerformQuery(TorznabQuery query)
{
var releases = new List<ReleaseInfo>();
NameValueCollection qParams = new NameValueCollection();
qParams.Add("tor[text]", query.GetQueryString());
qParams.Add("tor[srchIn][title]", "true");
qParams.Add("tor[srchIn][author]", "true");
qParams.Add("tor[searchType]", "all");
qParams.Add("tor[searchIn]", "torrents");
qParams.Add("tor[hash]", "");
qParams.Add("tor[sortType]", "default");
qParams.Add("tor[startNumber]", "0");
List<string> catList = MapTorznabCapsToTrackers(query);
if (catList.Any())
{
foreach (string cat in catList)
{
qParams.Add("tor[cat][]", cat);
}
}
else
{
qParams.Add("tor[cat][]", "0");
}
string urlSearch = SearchUrl;
if (qParams.Count > 0)
{
urlSearch += $"?{qParams.GetQueryString()}";
}
var response = await RequestStringWithCookiesAndRetry(urlSearch);
try
{
CQ dom = response.Content;
var rows = dom["table[class='newTorTable'] > tbody > tr"];
foreach (IDomObject row in rows)
{
CQ torrentData = row.OuterHTML;
CQ cells = row.Cq().Find("td");
if (cells.Any() && torrentData.Find("a[class='directDownload']").Any())
{
string title = torrentData.Find("a[class='title']").First().Text().Trim();
string author = torrentData.Find("a[class='author']").First().Text().Trim();
Uri link = new Uri(SiteLink + torrentData.Find("a[class='directDownload']").First().Attr("href").Trim().TrimStart('/'));
Uri guid = new Uri(SiteLink + torrentData.Find("a[class='directDownload']").First().Attr("href").Trim().TrimStart('/'));
long size = ReleaseInfo.GetBytes(cells.Elements.ElementAt(4).Cq().Text().Split('[')[1].TrimEnd(']'));
int seeders = ParseUtil.CoerceInt(cells.Elements.ElementAt(6).Cq().Find("p").ElementAt(0).Cq().Text());
int leechers = ParseUtil.CoerceInt(cells.Elements.ElementAt(6).Cq().Find("p").ElementAt(1).Cq().Text());
string pubDateStr = cells.Elements.ElementAt(5).Cq().Text().Split('[')[0];
DateTime publishDate = DateTime.Parse(pubDateStr).ToLocalTime();
long category = 0;
string cat = torrentData.Find("a[class='newCatLink']").First().Attr("href").Remove(0, "/tor/browse.php?tor[cat][]]=".Length);
long.TryParse(cat, out category);
var release = new ReleaseInfo();
release.Title = String.IsNullOrEmpty(author) ? title : String.Format("{0} by {1}", title, author);
release.Guid = guid;
release.Link = link;
release.PublishDate = publishDate;
release.Size = size;
release.Description = release.Title;
release.Seeders = seeders;
release.Peers = seeders + leechers;
release.MinimumRatio = 1;
release.MinimumSeedTime = 172800;
release.Category = MapTrackerCatToNewznab(category.ToString());
release.Comments = guid;
releases.Add(release);
}
}
}
catch (Exception ex)
{
OnParseError(response.Content, ex);
}
return releases;
}
}
}

View File

@@ -0,0 +1,167 @@
using CsQuery;
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.Globalization;
using System.Linq;
using System.Threading.Tasks;
using System.Web;
using Jackett.Models.IndexerConfig;
namespace Jackett.Indexers
{
public class PassThePopcorn : BaseIndexer, IIndexer
{
private string LoginUrl { get { return "https://tls.passthepopcorn.me/ajax.php?action=login"; } }
private string indexUrl { get { return "https://tls.passthepopcorn.me/ajax.php?action=login"; } }
private string SearchUrl { get { return "https://tls.passthepopcorn.me/torrents.php"; } }
private string DetailURL { get { return "https://tls.passthepopcorn.me/torrents.php?torrentid="; } }
private string AuthKey { get; set; }
new ConfigurationDataBasicLoginWithFilterAndPasskey configData
{
get { return (ConfigurationDataBasicLoginWithFilterAndPasskey)base.configData; }
set { base.configData = value; }
}
public PassThePopcorn(IIndexerManagerService i, Logger l, IWebClient c, IProtectionService ps)
: base(name: "PassThePopcorn",
description: "PassThePopcorn",
link: "https://passthepopcorn.me/",
caps: new TorznabCapabilities(),
manager: i,
client: c,
logger: l,
p: ps,
configData: new ConfigurationDataBasicLoginWithFilterAndPasskey(@"Enter filter options below to restrict search results.
Separate options with a space if using more than one option.<br>Filter options available:
<br><code>GoldenPopcorn</code><br><code>Scene</code><br><code>Checked</code>"))
{
AddCategoryMapping(1, TorznabCatType.Movies);
AddCategoryMapping(1, TorznabCatType.MoviesForeign);
AddCategoryMapping(1, TorznabCatType.MoviesOther);
AddCategoryMapping(1, TorznabCatType.MoviesSD);
AddCategoryMapping(1, TorznabCatType.MoviesHD);
AddCategoryMapping(1, TorznabCatType.Movies3D);
AddCategoryMapping(1, TorznabCatType.MoviesBluRay);
AddCategoryMapping(1, TorznabCatType.MoviesDVD);
AddCategoryMapping(1, TorznabCatType.MoviesWEBDL);
}
public async Task<IndexerConfigurationStatus> ApplyConfiguration(JToken configJson)
{
configData.LoadValuesFromJson(configJson);
await DoLogin();
return IndexerConfigurationStatus.RequiresTesting;
}
private async Task DoLogin()
{
var pairs = new Dictionary<string, string> {
{ "username", configData.Username.Value },
{ "password", configData.Password.Value },
{ "passkey", configData.Passkey.Value },
{ "keeplogged", "1" },
{ "login", "Log In!" }
};
var response = await RequestLoginAndFollowRedirect(LoginUrl, pairs, null, true, indexUrl, SiteLink);
JObject js_response = JObject.Parse(response.Content);
await ConfigureIfOK(response.Cookies, response.Content != null && (string)js_response["Result"] != "Error", () =>
{
// Landing page wil have "Result":"Error" if log in fails
string errorMessage = (string)js_response["Message"];
throw new ExceptionWithConfigData(errorMessage, configData);
});
}
public async Task<IEnumerable<ReleaseInfo>> PerformQuery(TorznabQuery query)
{
await DoLogin();
var releases = new List<ReleaseInfo>();
bool configGoldenPopcornOnly = configData.FilterString.Value.ToLowerInvariant().Contains("goldenpopcorn");
bool configSceneOnly = configData.FilterString.Value.ToLowerInvariant().Contains("scene");
bool configCheckedOnly = configData.FilterString.Value.ToLowerInvariant().Contains("checked");
string movieListSearchUrl;
if (string.IsNullOrEmpty(query.GetQueryString()))
movieListSearchUrl = string.Format("{0}?json=noredirect", SearchUrl);
else
{
if (!string.IsNullOrEmpty(query.ImdbID))
{
movieListSearchUrl = string.Format("{0}?json=noredirect&searchstr={1}", SearchUrl, HttpUtility.UrlEncode(query.ImdbID));
}
else
{
movieListSearchUrl = string.Format("{0}?json=noredirect&searchstr={1}", SearchUrl, HttpUtility.UrlEncode(query.GetQueryString()));
}
}
var results = await RequestStringWithCookiesAndRetry(movieListSearchUrl);
try
{
//Iterate over the releases for each movie
JObject js_results = JObject.Parse(results.Content);
foreach (var movie in js_results["Movies"])
{
string movie_title = (string) movie["Title"];
long movie_imdbid = long.Parse((string)movie["ImdbId"]);
string movie_groupid = (string)movie["GroupId"];
foreach (var torrent in movie["Torrents"])
{
var release = new ReleaseInfo();
release.Title = movie_title;
release.Description = release.Title;
release.Imdb = movie_imdbid;
release.Comments = new Uri(string.Format("{0}?id={1}", SearchUrl, HttpUtility.UrlEncode(movie_groupid)));
release.Guid = release.Comments;
release.Size = long.Parse((string)torrent["Size"]);
release.Seeders = int.Parse((string)torrent["Seeders"]);
release.Peers = int.Parse((string)torrent["Leechers"]);
release.PublishDate = DateTime.ParseExact((string)torrent["UploadTime"], "yyyy-MM-dd HH:mm:ss",
CultureInfo.InvariantCulture, DateTimeStyles.AssumeUniversal).ToLocalTime();
release.Link = new Uri(string.Format("{0}?action=download&id={1}&authkey={2}&torrent_pass={3}",
SearchUrl, HttpUtility.UrlEncode((string)torrent["Id"]), HttpUtility.UrlEncode(AuthKey), HttpUtility.UrlEncode(configData.Passkey.Value)));
release.MinimumRatio = 1;
release.MinimumSeedTime = 345600;
release.Category = 2000;
bool golden, scene, check;
bool.TryParse((string)torrent["GoldenPopcorn"], out golden);
bool.TryParse((string)torrent["Scene"], out scene);
bool.TryParse((string)torrent["Checked"], out check);
if (configGoldenPopcornOnly && !golden)
{
continue; //Skip release if user only wants GoldenPopcorn
}
if (configSceneOnly && !scene)
{
continue; //Skip release if user only wants Scene
}
if (configCheckedOnly && !check)
{
continue; //Skip release if user only wants Checked
}
releases.Add(release);
}
}
}
catch (Exception ex)
{
OnParseError(results.Content, ex);
}
return releases;
}
}
}

View File

@@ -0,0 +1,361 @@
using CsQuery;
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.Globalization;
using System.Linq;
using System.Net;
using System.Net.Http;
using System.Text;
using System.Threading.Tasks;
using System.Web;
using Jackett.Models.IndexerConfig;
using System.Text.RegularExpressions;
using System.Xml.Linq;
namespace Jackett.Indexers
{
public class RevolutionTT : BaseIndexer, IIndexer
{
private string LandingPageURL { get { return SiteLink + "login.php"; } }
private string LoginUrl { get { return SiteLink + "takelogin.php"; } }
private string GetRSSKeyUrl { get { return SiteLink + "getrss.php"; } }
private string RSSUrl { get { return SiteLink + "rss.php?feed=dl&passkey="; } }
private string SearchUrl { get { return SiteLink + "browse.php"; } }
private string DetailsURL { get { return SiteLink + "details.php?id={0}&hit=1"; } }
new ConfigurationDataBasicLoginWithRSS configData
{
get { return (ConfigurationDataBasicLoginWithRSS)base.configData; }
set { base.configData = value; }
}
public RevolutionTT(IIndexerManagerService i, Logger l, IWebClient wc, IProtectionService ps)
: base(name: "RevolutionTT",
description: "The Revolution has begun",
link: "https://revolutiontt.me/",
caps: TorznabUtil.CreateDefaultTorznabTVCaps(),
manager: i,
client: wc,
logger: l,
p: ps,
downloadBase: "https://revolutiontt.me/download.php/",
configData: new ConfigurationDataBasicLoginWithRSS())
{
/* Original RevolutionTT Categories -
Anime - 23
Appz/Misc - 22
Appz/PC-ISO - 1
E-Book - 36
Games/PC-ISO - 4
Games/PC-Rips - 21
Games/PS3 - 16
Games/Wii - 40
Games/XBOX360 - 39
Handheld/NDS - 35
Handheld/PSP - 34
Mac - 2
Movies/BluRay - 10
Movies/DVDR - 20
Movies/HDx264 - 12
Movies/Packs - 44
Movies/SDx264 - 11
Movies/XviD - 19
Music - 6
Music/FLAC - 8
Music/Packs - 46
MusicVideos - 29
TV/DVDR - 43
TV/HDx264 - 42
TV/Packs - 45
TV/SDx264 - 41
TV/XViD - 7
*/
//AddCategoryMapping("cat_id", TorznabCatType.Console);
AddCategoryMapping("35", TorznabCatType.ConsoleNDS);
AddCategoryMapping("34", TorznabCatType.ConsolePSP);
AddCategoryMapping("40", TorznabCatType.ConsoleWii);
//AddCategoryMapping("cat_id", TorznabCatType.ConsoleXbox);
AddCategoryMapping("39", TorznabCatType.ConsoleXbox360);
//AddCategoryMapping("cat_id", TorznabCatType.ConsoleWiiwareVC);
//AddCategoryMapping("cat_id", TorznabCatType.ConsoleXBOX360DLC);
AddCategoryMapping("16", TorznabCatType.ConsolePS3);
//AddCategoryMapping("cat_id", TorznabCatType.ConsoleOther);
//AddCategoryMapping("cat_id", TorznabCatType.Console3DS);
//AddCategoryMapping("cat_id", TorznabCatType.ConsolePSVita);
AddCategoryMapping("40", TorznabCatType.ConsoleWiiU);
//AddCategoryMapping("cat_id", TorznabCatType.ConsoleXboxOne);
//AddCategoryMapping("cat_id", TorznabCatType.ConsolePS4);
AddCategoryMapping("44", TorznabCatType.Movies);
//AddCategoryMapping("cat_id", TorznabCatType.MoviesForeign);
//AddCategoryMapping("cat_id", TorznabCatType.MoviesOther);
//Movies/DVDR, Movies/SDx264, Movies/XviD
AddMultiCategoryMapping(TorznabCatType.MoviesSD, 20, 11, 19);
//Movies/BluRay, Movies/HDx264
AddMultiCategoryMapping(TorznabCatType.MoviesHD, 10, 12);
//AddCategoryMapping("cat_id", TorznabCatType.Movies3D);
AddCategoryMapping("10", TorznabCatType.MoviesBluRay);
AddCategoryMapping("20", TorznabCatType.MoviesDVD);
//AddCategoryMapping("cat_id", TorznabCatType.MoviesWEBDL);
//Music, Music/Packs
AddMultiCategoryMapping(TorznabCatType.Audio, 6, 46);
//AddCategoryMapping("cat_id", TorznabCatType.AudioMP3);
AddCategoryMapping("29", TorznabCatType.AudioVideo);
//AddCategoryMapping("cat_id", TorznabCatType.AudioAudiobook);
AddCategoryMapping("8", TorznabCatType.AudioLossless);
//AddCategoryMapping("cat_id", TorznabCatType.AudioOther);
//AddCategoryMapping("cat_id", TorznabCatType.AudioForeign);
AddCategoryMapping("21", TorznabCatType.PC);
AddCategoryMapping("22", TorznabCatType.PC0day);
AddCategoryMapping("4", TorznabCatType.PCISO);
AddCategoryMapping("2", TorznabCatType.PCMac);
//AddCategoryMapping("cat_id", TorznabCatType.PCPhoneOther);
//Games/PC-ISO, Games/PC-Rips
AddMultiCategoryMapping(TorznabCatType.PCGames, 4, 21);
//AddCategoryMapping("cat_id", TorznabCatType.PCPhoneIOS);
//AddCategoryMapping("cat_id", TorznabCatType.PCPhoneAndroid);
AddCategoryMapping("45", TorznabCatType.TV);
//AddCategoryMapping("cat_id", TorznabCatType.TVWEBDL);
//AddCategoryMapping("cat_id", TorznabCatType.TVFOREIGN);
//TV/DVDR, TV/SDx264, TV/XViD
AddMultiCategoryMapping(TorznabCatType.TVSD, 43, 41, 7);
AddCategoryMapping("42", TorznabCatType.TVHD);
//AddCategoryMapping("cat_id", TorznabCatType.TVOTHER);
//AddCategoryMapping("cat_id", TorznabCatType.TVSport);
AddCategoryMapping("23", TorznabCatType.TVAnime);
//AddCategoryMapping("cat_id", TorznabCatType.TVDocumentary);
//AddCategoryMapping("cat_id", TorznabCatType.XXX);
//AddCategoryMapping("cat_id", TorznabCatType.XXXDVD);
//AddCategoryMapping("cat_id", TorznabCatType.XXXWMV);
//AddCategoryMapping("cat_id", TorznabCatType.XXXXviD);
//AddCategoryMapping("cat_id", TorznabCatType.XXXx264);
//AddCategoryMapping("cat_id", TorznabCatType.XXXOther);
//AddCategoryMapping("cat_id", TorznabCatType.XXXImageset);
//AddCategoryMapping("cat_id", TorznabCatType.XXXPacks);
//AddCategoryMapping("cat_id", TorznabCatType.Other);
//AddCategoryMapping("cat_id", TorznabCatType.OtherMisc);
//AddCategoryMapping("cat_id", TorznabCatType.OtherHashed);
AddCategoryMapping("36", TorznabCatType.Books);
AddCategoryMapping("36", TorznabCatType.BooksEbook);
//AddCategoryMapping("cat_id", TorznabCatType.BooksComics);
//AddCategoryMapping("cat_id", TorznabCatType.BooksMagazines);
//AddCategoryMapping("cat_id", TorznabCatType.BooksTechnical);
//AddCategoryMapping("cat_id", TorznabCatType.BooksOther);
//AddCategoryMapping("cat_id", TorznabCatType.BooksForeign);
// RSS Textual categories
AddCategoryMapping("Anime", TorznabCatType.TVAnime);
AddCategoryMapping("Appz/Misc", TorznabCatType.PC0day);
AddCategoryMapping("Appz/PC-ISO", TorznabCatType.Books);
AddCategoryMapping("E-Book", TorznabCatType.BooksEbook);
AddCategoryMapping("Games/PC-ISO", TorznabCatType.PCGames);
AddCategoryMapping("Games/PC-Rips", TorznabCatType.PCGames);
AddCategoryMapping("Games/PS3", TorznabCatType.ConsolePS3);
AddCategoryMapping("Games/Wii", TorznabCatType.ConsoleWii);
AddCategoryMapping("Games/XBOX360", TorznabCatType.ConsoleXbox360);
AddCategoryMapping("Handheld/NDS", TorznabCatType.ConsoleNDS);
AddCategoryMapping("Handheld/PSP", TorznabCatType.ConsolePSP);
AddCategoryMapping("Mac", TorznabCatType.PCMac);
AddCategoryMapping("Movies/BluRay", TorznabCatType.MoviesBluRay);
AddCategoryMapping("Movies/DVDR", TorznabCatType.MoviesDVD);
AddCategoryMapping("Movies/HDx264", TorznabCatType.MoviesHD);
AddCategoryMapping("Movies/Packs", TorznabCatType.Movies);
AddCategoryMapping("Movies/SDx264", TorznabCatType.MoviesSD);
AddCategoryMapping("Movies/XviD", TorznabCatType.MoviesSD);
AddCategoryMapping("Music", TorznabCatType.Audio);
AddCategoryMapping("Music/FLAC", TorznabCatType.AudioLossless);
AddCategoryMapping("Music/Packs", TorznabCatType.AudioOther);
AddCategoryMapping("MusicVideos", TorznabCatType.AudioVideo);
AddCategoryMapping("TV/DVDR", TorznabCatType.TV);
AddCategoryMapping("TV/HDx264", TorznabCatType.TVHD);
AddCategoryMapping("TV/Packs", TorznabCatType.TV);
AddCategoryMapping("TV/SDx264", TorznabCatType.TVSD);
AddCategoryMapping("TV/XViD", TorznabCatType.TVSD);
}
public async Task<IndexerConfigurationStatus> ApplyConfiguration(JToken configJson)
{
configData.LoadValuesFromJson(configJson);
var pairs = new Dictionary<string, string> {
{ "username", configData.Username.Value },
{ "password", configData.Password.Value }
};
// need to do an initial request to get PHP session cookie (any better way to do this?)
var homePageLoad = await RequestLoginAndFollowRedirect(LandingPageURL, new Dictionary<string, string> { }, null, true, null, SiteLink);
var result = await RequestLoginAndFollowRedirect(LoginUrl, pairs, homePageLoad.Cookies, true, null, LandingPageURL);
await ConfigureIfOK(result.Cookies, result.Content != null && result.Content.Contains("/logout.php"), () =>
{
CQ dom = result.Content;
var messageEl = dom[".error"];
var errorMessage = messageEl.Text().Trim();
throw new ExceptionWithConfigData(errorMessage, configData);
});
// Store RSS key from feed generator page
try
{
var rssParams = new Dictionary<string, string> {
{ "feed", "dl" }
};
var rssPage = await PostDataWithCookies(GetRSSKeyUrl, rssParams, result.Cookies);
var match = Regex.Match(rssPage.Content, "(?<=passkey\\=)([a-zA-z0-9]*)");
configData.RSSKey.Value = match.Success ? match.Value : string.Empty;
if (string.IsNullOrWhiteSpace(configData.RSSKey.Value))
throw new Exception("Failed to get RSS Key");
SaveConfig();
}
catch (Exception e)
{
IsConfigured = false;
throw e;
}
return IndexerConfigurationStatus.RequiresTesting;
}
public async Task<IEnumerable<ReleaseInfo>> PerformQuery(TorznabQuery query)
{
var releases = new List<ReleaseInfo>();
var searchString = query.GetQueryString();
var searchUrl = SearchUrl;
// If query is empty, use the RSS Feed
if (string.IsNullOrWhiteSpace(searchString))
{
var rssPage = await RequestStringWithCookiesAndRetry(RSSUrl + configData.RSSKey.Value);
var rssDoc = XDocument.Parse(rssPage.Content);
foreach (var item in rssDoc.Descendants("item"))
{
var title = item.Descendants("title").First().Value;
var description = item.Descendants("description").First().Value;
var link = item.Descendants("link").First().Value;
var date = item.Descendants("pubDate").First().Value;
var torrentIdMatch = Regex.Match(link, "(?<=download\\.php/)([a-zA-z0-9]*)");
var torrentId = torrentIdMatch.Success ? torrentIdMatch.Value : string.Empty;
if (string.IsNullOrWhiteSpace(torrentId))
throw new Exception("Missing torrent id");
var infoMatch = Regex.Match(description, @"Category:\W(?<cat>.*)\W\n\WSize:\W(?<size>.*)\n\WStatus:\W(?<seeders>.*)\Wseeder(.*)\Wand\W(?<leechers>.*)\Wleecher(.*)\n\WAdded:\W(?<added>.*)\n\WDescription:");
if (!infoMatch.Success)
throw new Exception("Unable to find info");
var imdbMatch = Regex.Match(description, "(?<=http://www.imdb.com/title/tt)([0-9]*)");
long? imdbID = null;
if(imdbMatch.Success)
{
long l;
if(long.TryParse(imdbMatch.Value, out l))
{
imdbID = l;
}
}
var release = new ReleaseInfo()
{
Title = title,
Description = title,
Guid = new Uri(string.Format(DetailsURL, torrentId)),
Comments = new Uri(string.Format(DetailsURL, torrentId) + "&tocomm=1"),
PublishDate = DateTime.ParseExact(infoMatch.Groups["added"].Value, "yyyy-MM-dd H:mm:ss", CultureInfo.InvariantCulture), //2015-08-08 21:20:31
Link = new Uri(link),
Seeders = ParseUtil.CoerceInt(infoMatch.Groups["seeders"].Value == "no" ? "0" : infoMatch.Groups["seeders"].Value),
Peers = ParseUtil.CoerceInt(infoMatch.Groups["leechers"].Value == "no" ? "0" : infoMatch.Groups["leechers"].Value),
Size = ReleaseInfo.GetBytes(infoMatch.Groups["size"].Value),
Category = MapTrackerCatToNewznab(infoMatch.Groups["cat"].Value),
Imdb = imdbID
};
// if unknown category, set to "other"
if (release.Category == 0)
release.Category = 7000;
release.Peers += release.Seeders;
releases.Add(release);
}
}
else
{
searchUrl += "?titleonly=1&search=" + HttpUtility.UrlEncode(searchString);
string.Format(SearchUrl, HttpUtility.UrlEncode(searchString));
var cats = MapTorznabCapsToTrackers(query);
if (cats.Count > 0)
{
foreach (var cat in cats)
{
searchUrl += "&c" + cat + "=1";
}
}
var results = await RequestStringWithCookiesAndRetry(searchUrl);
try
{
CQ dom = results.Content;
// table header is the first <tr> in table body, get all rows except this
CQ qRows = dom["#torrents-table > tbody > tr:not(:first-child)"];
foreach (var row in qRows)
{
var release = new ReleaseInfo();
var qRow = row.Cq();
var debug = qRow.Html();
release.MinimumRatio = 1;
release.MinimumSeedTime = 172800;
CQ qLink = qRow.Find(".br_right > a").First();
release.Guid = new Uri(SiteLink + qLink.Attr("href"));
release.Comments = new Uri(SiteLink + qLink.Attr("href") + "&tocomm=1");
release.Title = qLink.Find("b").Text();
release.Description = release.Title;
release.Link = new Uri(SiteLink + qRow.Find("td:nth-child(4) > a").Attr("href"));
var dateString = qRow.Find("td:nth-child(6) nobr")[0].InnerText.Trim();
//"2015-04-25 23:38:12"
//"yyyy-MMM-dd hh:mm:ss"
release.PublishDate = DateTime.ParseExact(dateString, "yyyy-MM-ddHH:mm:ss", CultureInfo.InvariantCulture);
var sizeStr = qRow.Children().ElementAt(6).InnerText.Trim();
release.Size = ReleaseInfo.GetBytes(sizeStr);
release.Seeders = ParseUtil.CoerceInt(qRow.Find("td:nth-child(9)").Text());
release.Peers = release.Seeders + ParseUtil.CoerceInt(qRow.Find("td:nth-child(10)").Text());
var category = qRow.Find(".br_type > a").Attr("href").Replace("browse.php?cat=", string.Empty);
release.Category = MapTrackerCatToNewznab(category);
releases.Add(release);
}
}
catch (Exception ex)
{
OnParseError(results.Content, ex);
}
}
return releases;
}
}
}

View File

@@ -1,164 +1,165 @@
using CsQuery;
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.Globalization;
using System.Net;
using System.Net.Http;
using System.Text;
using System.Threading.Tasks;
using Jackett.Models.IndexerConfig;
namespace Jackett.Indexers
{
class SceneAccess : BaseIndexer, IIndexer
{
private string LoginUrl { get { return SiteLink + "login"; } }
private string SearchUrl { get { return SiteLink + "all?search={0}&method=2"; } }
new ConfigurationDataBasicLogin configData
{
get { return (ConfigurationDataBasicLogin)base.configData; }
set { base.configData = value; }
}
public SceneAccess(IIndexerManagerService i, IWebClient c, Logger l, IProtectionService ps)
: base(name: "SceneAccess",
description: "Your gateway to the scene",
link: "https://sceneaccess.eu/",
caps: TorznabUtil.CreateDefaultTorznabTVCaps(),
manager: i,
client: c,
logger: l,
p: ps,
configData: new ConfigurationDataBasicLogin())
{
AddCategoryMapping(8, TorznabCatType.MoviesSD);
AddCategoryMapping(22, TorznabCatType.MoviesHD);
AddCategoryMapping(7, TorznabCatType.MoviesSD);
AddCategoryMapping(4, TorznabCatType.Movies);
AddCategoryMapping(27, TorznabCatType.TVHD);
AddCategoryMapping(17, TorznabCatType.TVSD);
AddCategoryMapping(11, TorznabCatType.MoviesSD);
AddCategoryMapping(26, TorznabCatType.TV);
AddCategoryMapping(3, TorznabCatType.PCGames);
AddCategoryMapping(5, TorznabCatType.ConsolePS3);
AddCategoryMapping(20, TorznabCatType.ConsolePSP);
AddCategoryMapping(28, TorznabCatType.TV);
AddCategoryMapping(23, TorznabCatType.Console);
AddCategoryMapping(29, TorznabCatType.Console);
AddCategoryMapping(40, TorznabCatType.AudioLossless);
AddCategoryMapping(13, TorznabCatType.AudioMP3);
AddCategoryMapping(15, TorznabCatType.AudioVideo);
AddCategoryMapping(1, TorznabCatType.PCISO);
AddCategoryMapping(2, TorznabCatType.PCISO);
AddCategoryMapping(14, TorznabCatType.PCISO);
AddCategoryMapping(21, TorznabCatType.Other);
AddCategoryMapping(41, TorznabCatType.MoviesHD);
AddCategoryMapping(42, TorznabCatType.MoviesSD);
AddCategoryMapping(43, TorznabCatType.MoviesSD);
AddCategoryMapping(44, TorznabCatType.TVHD);
AddCategoryMapping(45, TorznabCatType.TVSD);
AddCategoryMapping(12, TorznabCatType.XXXXviD);
AddCategoryMapping(35, TorznabCatType.XXXx264);
AddCategoryMapping(36, TorznabCatType.XXX);
AddCategoryMapping(30, TorznabCatType.MoviesForeign);
AddCategoryMapping(31, TorznabCatType.MoviesForeign);
AddCategoryMapping(32, TorznabCatType.MoviesForeign);
AddCategoryMapping(33, TorznabCatType.TVFOREIGN);
AddCategoryMapping(34, TorznabCatType.TVFOREIGN);
AddCategoryMapping(4, TorznabCatType.Movies);
AddCategoryMapping(37, TorznabCatType.XXX);
AddCategoryMapping(38, TorznabCatType.Audio);
}
public async Task<IndexerConfigurationStatus> ApplyConfiguration(JToken configJson)
{
configData.LoadValuesFromJson(configJson);
var pairs = new Dictionary<string, string> {
{ "username", configData.Username.Value },
{ "password", configData.Password.Value },
{ "submit", "come on in" }
};
var loginPage = await RequestStringWithCookies(LoginUrl, string.Empty);
var result = await RequestLoginAndFollowRedirect(LoginUrl, pairs, loginPage.Cookies, true, SiteLink, LoginUrl);
await ConfigureIfOK(result.Cookies + " " + loginPage.Cookies, result.Content != null && result.Content.Contains("nav_profile"), () =>
{
CQ dom = result.Content;
var messageEl = dom["#login_box_desc"];
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>();
var results = await RequestStringWithCookiesAndRetry(string.Format(SearchUrl, query.GetQueryString()));
try
{
CQ dom = results.Content;
var rows = dom["#torrents-table > tbody > tr.tt_row"];
foreach (var row in rows)
{
CQ qRow = row.Cq();
var release = new ReleaseInfo();
release.MinimumRatio = 1;
release.MinimumSeedTime = 129600;
release.Title = qRow.Find(".ttr_name > a").Text();
release.Description = release.Title;
release.Guid = new Uri(SiteLink + qRow.Find(".ttr_name > a").Attr("href"));
release.Comments = release.Guid;
release.Link = new Uri(SiteLink + qRow.Find(".td_dl > a").Attr("href"));
var sizeStr = qRow.Find(".ttr_size").Contents()[0].NodeValue;
release.Size = ReleaseInfo.GetBytes(sizeStr);
var timeStr = qRow.Find(".ttr_added").Text();
DateTime time;
if (DateTime.TryParseExact(timeStr, "yyyy-MM-ddHH:mm:ss", CultureInfo.InvariantCulture, DateTimeStyles.None, out time))
{
release.PublishDate = time;
}
release.Seeders = ParseUtil.CoerceInt(qRow.Find(".ttr_seeders").Text());
release.Peers = ParseUtil.CoerceInt(qRow.Find(".ttr_leechers").Text()) + release.Seeders;
var cat = qRow.Find(".ttr_type a").Attr("href").Replace("?cat=",string.Empty);
release.Category = MapTrackerCatToNewznab(cat);
releases.Add(release);
}
}
catch (Exception ex)
{
OnParseError(results.Content, ex);
}
return releases;
}
}
}
using CsQuery;
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.Globalization;
using System.Net;
using System.Net.Http;
using System.Text;
using System.Threading.Tasks;
using Jackett.Models.IndexerConfig;
using System.Web;
namespace Jackett.Indexers
{
class SceneAccess : BaseIndexer, IIndexer
{
private string LoginUrl { get { return SiteLink + "login"; } }
private string SearchUrl { get { return SiteLink + "all?search={0}&method=2"; } }
new ConfigurationDataBasicLogin configData
{
get { return (ConfigurationDataBasicLogin)base.configData; }
set { base.configData = value; }
}
public SceneAccess(IIndexerManagerService i, IWebClient c, Logger l, IProtectionService ps)
: base(name: "SceneAccess",
description: "Your gateway to the scene",
link: "https://sceneaccess.eu/",
caps: TorznabUtil.CreateDefaultTorznabTVCaps(),
manager: i,
client: c,
logger: l,
p: ps,
configData: new ConfigurationDataBasicLogin())
{
AddCategoryMapping(8, TorznabCatType.MoviesSD);
AddCategoryMapping(22, TorznabCatType.MoviesHD);
AddCategoryMapping(7, TorznabCatType.MoviesSD);
AddCategoryMapping(4, TorznabCatType.Movies);
AddCategoryMapping(27, TorznabCatType.TVHD);
AddCategoryMapping(17, TorznabCatType.TVSD);
AddCategoryMapping(11, TorznabCatType.MoviesSD);
AddCategoryMapping(26, TorznabCatType.TV);
AddCategoryMapping(3, TorznabCatType.PCGames);
AddCategoryMapping(5, TorznabCatType.ConsolePS3);
AddCategoryMapping(20, TorznabCatType.ConsolePSP);
AddCategoryMapping(28, TorznabCatType.TV);
AddCategoryMapping(23, TorznabCatType.Console);
AddCategoryMapping(29, TorznabCatType.Console);
AddCategoryMapping(40, TorznabCatType.AudioLossless);
AddCategoryMapping(13, TorznabCatType.AudioMP3);
AddCategoryMapping(15, TorznabCatType.AudioVideo);
AddCategoryMapping(1, TorznabCatType.PCISO);
AddCategoryMapping(2, TorznabCatType.PCISO);
AddCategoryMapping(14, TorznabCatType.PCISO);
AddCategoryMapping(21, TorznabCatType.Other);
AddCategoryMapping(41, TorznabCatType.MoviesHD);
AddCategoryMapping(42, TorznabCatType.MoviesSD);
AddCategoryMapping(43, TorznabCatType.MoviesSD);
AddCategoryMapping(44, TorznabCatType.TVHD);
AddCategoryMapping(45, TorznabCatType.TVSD);
AddCategoryMapping(12, TorznabCatType.XXXXviD);
AddCategoryMapping(35, TorznabCatType.XXXx264);
AddCategoryMapping(36, TorznabCatType.XXX);
AddCategoryMapping(30, TorznabCatType.MoviesForeign);
AddCategoryMapping(31, TorznabCatType.MoviesForeign);
AddCategoryMapping(32, TorznabCatType.MoviesForeign);
AddCategoryMapping(33, TorznabCatType.TVFOREIGN);
AddCategoryMapping(34, TorznabCatType.TVFOREIGN);
AddCategoryMapping(4, TorznabCatType.Movies);
AddCategoryMapping(37, TorznabCatType.XXX);
AddCategoryMapping(38, TorznabCatType.Audio);
}
public async Task<IndexerConfigurationStatus> ApplyConfiguration(JToken configJson)
{
configData.LoadValuesFromJson(configJson);
var pairs = new Dictionary<string, string> {
{ "username", configData.Username.Value },
{ "password", configData.Password.Value },
{ "submit", "come on in" }
};
var loginPage = await RequestStringWithCookies(LoginUrl, string.Empty);
var result = await RequestLoginAndFollowRedirect(LoginUrl, pairs, loginPage.Cookies, true, SiteLink, LoginUrl);
await ConfigureIfOK(result.Cookies + " " + loginPage.Cookies, result.Content != null && result.Content.Contains("nav_profile"), () =>
{
CQ dom = result.Content;
var messageEl = dom["#login_box_desc"];
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>();
var results = await RequestStringWithCookiesAndRetry(string.Format(SearchUrl, HttpUtility.UrlEncode(query.GetQueryString())));
try
{
CQ dom = results.Content;
var rows = dom["#torrents-table > tbody > tr.tt_row"];
foreach (var row in rows)
{
CQ qRow = row.Cq();
var release = new ReleaseInfo();
release.MinimumRatio = 1;
release.MinimumSeedTime = 129600;
release.Title = qRow.Find(".ttr_name > a").Text();
release.Description = release.Title;
release.Guid = new Uri(SiteLink + qRow.Find(".ttr_name > a").Attr("href"));
release.Comments = release.Guid;
release.Link = new Uri(SiteLink + qRow.Find(".td_dl > a").Attr("href"));
var sizeStr = qRow.Find(".ttr_size").Contents()[0].NodeValue;
release.Size = ReleaseInfo.GetBytes(sizeStr);
var timeStr = qRow.Find(".ttr_added").Text();
DateTime time;
if (DateTime.TryParseExact(timeStr, "yyyy-MM-ddHH:mm:ss", CultureInfo.InvariantCulture, DateTimeStyles.None, out time))
{
release.PublishDate = time;
}
release.Seeders = ParseUtil.CoerceInt(qRow.Find(".ttr_seeders").Text());
release.Peers = ParseUtil.CoerceInt(qRow.Find(".ttr_leechers").Text()) + release.Seeders;
var cat = qRow.Find(".ttr_type a").Attr("href").Replace("?cat=",string.Empty);
release.Category = MapTrackerCatToNewznab(cat);
releases.Add(release);
}
}
catch (Exception ex)
{
OnParseError(results.Content, ex);
}
return releases;
}
}
}

View File

@@ -0,0 +1,125 @@
using Jackett.Utils.Clients;
using NLog;
using Jackett.Services;
using Jackett.Utils;
using Jackett.Models;
using System.Threading.Tasks;
using Newtonsoft.Json.Linq;
using System.Collections.Generic;
using CsQuery;
using System.Web;
using System;
using System.Globalization;
using Jackett.Models.IndexerConfig;
namespace Jackett.Indexers
{
public class SceneFZ : BaseIndexer, IIndexer
{
string LoginUrl { get { return SiteLink + "takelogin.php"; } }
string BrowseUrl { get { return SiteLink + "ajax_browse.php"; } }
new ConfigurationDataBasicLogin configData
{
get { return (ConfigurationDataBasicLogin)base.configData; }
set { base.configData = value; }
}
public SceneFZ(IIndexerManagerService i, IWebClient wc, Logger l, IProtectionService ps)
: base(name: "SceneFZ",
description: "Torrent tracker. Tracking over 50.000 torrent files.",
link: "http://scenefz.net/",
caps: TorznabUtil.CreateDefaultTorznabTVCaps(),
manager: i,
client: wc,
logger: l,
p: ps,
configData: new ConfigurationDataBasicLogin())
{
AddCategoryMapping(32, TorznabCatType.Movies);
AddCategoryMapping(33, TorznabCatType.TV);
}
public async Task<IndexerConfigurationStatus> ApplyConfiguration(JToken configJson)
{
configData.LoadValuesFromJson(configJson);
var pairs = new Dictionary<string, string>
{
{ "username", configData.Username.Value },
{ "password", configData.Password.Value }
};
var result = await RequestLoginAndFollowRedirect(LoginUrl, pairs, null, true, null, LoginUrl);
await ConfigureIfOK(result.Cookies, result.Content != null && result.Content.Contains("Please wait..."), () =>
{
CQ dom = result.Content;
var errorMessage = dom[".tableinborder:eq(1) td"].Text().Trim();
throw new ExceptionWithConfigData(errorMessage, configData);
});
return IndexerConfigurationStatus.RequiresTesting;
}
public async Task<IEnumerable<ReleaseInfo>> PerformQuery(TorznabQuery query)
{
var releases = new List<ReleaseInfo>();
var searchUrl = BrowseUrl;
var searchString = query.GetQueryString();
var cats = MapTorznabCapsToTrackers(query);
string cat = "0";
if (cats.Count == 1)
{
cat = cats[0];
}
if (!string.IsNullOrWhiteSpace(searchString) || cat != "0")
searchUrl += string.Format("?search={0}&param_val=0&complex_search=0&incldead=mc{1}&orderby=added&sort=desc", HttpUtility.UrlEncode(searchString), cat);
var response = await RequestStringWithCookiesAndRetry(searchUrl, null, BrowseUrl);
var results = response.Content;
try
{
CQ dom = results;
var rows = dom["td#browse-middle-td"];
foreach (var row in rows)
{
var release = new ReleaseInfo();
var qRow = row.Cq();
var qTitleLink = qRow.Find("table tbody tr:eq(0) td a").First();
release.Title = qRow.Find("table tbody tr:eq(0) td a b").Text().Trim();
release.Description = release.Title;
release.Guid = new Uri(SiteLink + qTitleLink.Attr("href"));
release.Comments = release.Guid;
//24.04.2016 16:44:57
var dateStr = qRow.Find("table tbody tr:eq(1) td:eq(4)").Html().Replace("&nbsp;", " ").Trim();
release.PublishDate = DateTime.ParseExact(dateStr, "dd.MM.yyyy HH:mm:ss", CultureInfo.InvariantCulture).AddHours(-2);
var qLink = qRow.First().Next().Find("a");
release.Link = new Uri(SiteLink + qLink.Attr("href"));
var sizeStr = qRow.Find("table tbody tr:eq(1) td b").Text().Trim();
release.Size = ReleaseInfo.GetBytes(sizeStr.Replace(",", "."));
release.Seeders = ParseUtil.CoerceInt(qRow.Find("table tbody tr:eq(1) td:eq(1) b:eq(0) font").Text().Trim());
release.Peers = ParseUtil.CoerceInt(qRow.Find("table tbody tr:eq(1) td:eq(1) b:eq(1) font").Text().Trim()) + release.Seeders;
var catId = qRow.First().Prev().Find("a").Attr("onclick").Substring(21, 2);
release.Category = MapTrackerCatToNewznab(catId);
releases.Add(release);
}
}
catch (Exception ex)
{
OnParseError(results, ex);
}
return releases;
}
}
}

View File

@@ -7,14 +7,10 @@ using Newtonsoft.Json.Linq;
using NLog;
using System;
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
using System.Net;
using System.Net.Http;
using System.Text;
using System.Threading.Tasks;
using System.Web;
using Jackett.Models.IndexerConfig;
using System.Text.RegularExpressions;
namespace Jackett.Indexers
{
@@ -34,13 +30,52 @@ namespace Jackett.Indexers
: base(name: "SceneTime",
description: "Always on time",
link: "https://www.scenetime.com/",
caps: TorznabUtil.CreateDefaultTorznabTVCaps(),
caps: new TorznabCapabilities(),
manager: i,
client: w,
logger: l,
p: ps,
configData: new ConfigurationDataBasicLogin())
configData: new ConfigurationDataBasicLogin("For best results, change the 'Torrents per page' setting to the maximum in your profile on the SceneTime webpage."))
{
AddCategoryMapping(1, TorznabCatType.MoviesSD);
AddCategoryMapping(3, TorznabCatType.MoviesDVD);
AddCategoryMapping(47, TorznabCatType.MoviesSD);
AddCategoryMapping(57, TorznabCatType.MoviesSD);
AddCategoryMapping(59, TorznabCatType.MoviesHD);
AddCategoryMapping(61, TorznabCatType.MoviesSD);
AddCategoryMapping(64, TorznabCatType.Movies3D);
AddCategoryMapping(80, TorznabCatType.MoviesForeign);
AddCategoryMapping(81, TorznabCatType.MoviesBluRay);
AddCategoryMapping(82, TorznabCatType.MoviesOther);
AddCategoryMapping(102, TorznabCatType.MoviesOther);
AddCategoryMapping(103, TorznabCatType.MoviesWEBDL);
AddCategoryMapping(105, TorznabCatType.Movies);
AddCategoryMapping(6, TorznabCatType.PCGames);
AddCategoryMapping(48, TorznabCatType.ConsoleXbox);
AddCategoryMapping(49, TorznabCatType.ConsolePSP);
AddCategoryMapping(50, TorznabCatType.ConsolePS3);
AddCategoryMapping(51, TorznabCatType.ConsoleWii);
AddCategoryMapping(55, TorznabCatType.ConsoleNDS);
AddCategoryMapping(107, TorznabCatType.ConsolePS4);
AddCategoryMapping(2, TorznabCatType.TVSD);
AddCategoryMapping(43, TorznabCatType.TV);
AddCategoryMapping(9, TorznabCatType.TVHD);
AddCategoryMapping(63, TorznabCatType.TV);
AddCategoryMapping(77, TorznabCatType.TVSD);
AddCategoryMapping(79, TorznabCatType.TVSport);
AddCategoryMapping(100, TorznabCatType.TVFOREIGN);
AddCategoryMapping(83, TorznabCatType.TVWEBDL);
AddCategoryMapping(5, TorznabCatType.PC0day);
AddCategoryMapping(7, TorznabCatType.Books);
AddCategoryMapping(52, TorznabCatType.PCMac);
AddCategoryMapping(65, TorznabCatType.BooksComics);
AddCategoryMapping(53, TorznabCatType.PC);
AddCategoryMapping(4, TorznabCatType.Audio);
AddCategoryMapping(11, TorznabCatType.AudioVideo);
}
public async Task<IndexerConfigurationStatus> ApplyConfiguration(JToken configJson)
@@ -62,23 +97,45 @@ namespace Jackett.Indexers
return IndexerConfigurationStatus.RequiresTesting;
}
private Dictionary<string, string> GetSearchFormData(string searchString)
{
return new Dictionary<string, string> {
{ "c2", "1" }, { "c43", "1" }, { "c9", "1" }, { "c63", "1" }, { "c77", "1" }, { "c100", "1" }, { "c101", "1" },
{ "cata", "yes" }, { "sec", "jax" },
{ "search", searchString}
};
}
public async Task<IEnumerable<ReleaseInfo>> PerformQuery(TorznabQuery query)
{
var releases = new List<ReleaseInfo>();
var results = await PostDataWithCookiesAndRetry(SearchUrl, GetSearchFormData(query.GetQueryString()));
Dictionary<string, string> qParams = new Dictionary<string, string>();
qParams.Add("cata", "yes");
qParams.Add("sec", "jax");
List<string> catList = MapTorznabCapsToTrackers(query);
foreach (string cat in catList)
{
qParams.Add("c" + cat, "1");
}
if (!string.IsNullOrEmpty(query.SanitizedSearchTerm))
{
qParams.Add("search", query.GetQueryString());
}
var results = await PostDataWithCookiesAndRetry(SearchUrl, qParams);
List<ReleaseInfo> releases = ParseResponse(results.Content);
return releases;
}
public List<ReleaseInfo> ParseResponse(string htmlResponse)
{
List<ReleaseInfo> releases = new List<ReleaseInfo>();
try
{
CQ dom = results.Content;
CQ dom = htmlResponse;
List<string> headerColumns = dom["table[class*='movehere']"].First().Find("tbody > tr > td[class='cat_Head']").Select(x => x.Cq().Text()).ToList();
int categoryIndex = headerColumns.FindIndex(x => x.Equals("Type"));
int nameIndex = headerColumns.FindIndex(x => x.Equals("Name"));
int sizeIndex = headerColumns.FindIndex(x => x.Equals("Size"));
int seedersIndex = headerColumns.FindIndex(x => x.Equals("Seeders"));
int leechersIndex = headerColumns.FindIndex(x => x.Equals("Leechers"));
var rows = dom["tr.browse"];
foreach (var row in rows)
{
@@ -86,7 +143,12 @@ namespace Jackett.Indexers
release.MinimumRatio = 1;
release.MinimumSeedTime = 172800;
var descCol = row.ChildElements.ElementAt(1);
var categoryCol = row.ChildElements.ElementAt(categoryIndex);
string catLink = categoryCol.Cq().Find("a").Attr("href");
string catId = new Regex(@"\?cat=(\d*)").Match(catLink).Groups[1].ToString().Trim();
release.Category = MapTrackerCatToNewznab(catId);
var descCol = row.ChildElements.ElementAt(nameIndex);
var qDescCol = descCol.Cq();
var qLink = qDescCol.Find("a");
release.Title = qLink.Text();
@@ -98,19 +160,20 @@ namespace Jackett.Indexers
release.PublishDate = DateTimeUtil.FromTimeAgo(descCol.ChildNodes.Last().InnerText);
var sizeStr = row.ChildElements.ElementAt(5).Cq().Text();
var sizeStr = row.ChildElements.ElementAt(sizeIndex).Cq().Text();
release.Size = ReleaseInfo.GetBytes(sizeStr);
release.Seeders = ParseUtil.CoerceInt(row.ChildElements.ElementAt(6).Cq().Text().Trim());
release.Peers = ParseUtil.CoerceInt(row.ChildElements.ElementAt(7).Cq().Text().Trim()) + release.Seeders;
release.Seeders = ParseUtil.CoerceInt(row.ChildElements.ElementAt(seedersIndex).Cq().Text().Trim());
release.Peers = ParseUtil.CoerceInt(row.ChildElements.ElementAt(leechersIndex).Cq().Text().Trim()) + release.Seeders;
releases.Add(release);
}
}
catch (Exception ex)
{
OnParseError(results.Content, ex);
OnParseError(htmlResponse, ex);
}
return releases;
}
}

View File

@@ -8,18 +8,17 @@ using NLog;
using System;
using System.Collections.Generic;
using System.Globalization;
using System.Text.RegularExpressions;
using System.Linq;
using System.Threading.Tasks;
using Jackett.Models.IndexerConfig;
using System.Collections.Specialized;
namespace Jackett.Indexers
{
public class SpeedCD : BaseIndexer, IIndexer
{
private string LoginUrl { get { return SiteLink + "take.login.php"; } }
private string SearchUrl { get { return SiteLink + "V3/API/API.php"; } }
private string CommentsUrl { get { return SiteLink + "t/{0}"; } }
private string DownloadUrl { get { return SiteLink + "download.php?torrent={0}"; } }
private string LoginUrl { get { return SiteLink + "takeElogin.php"; } }
private string SearchUrl { get { return SiteLink + "browse.php"; } }
new ConfigurationDataBasicLogin configData
{
@@ -30,13 +29,14 @@ namespace Jackett.Indexers
public SpeedCD(IIndexerManagerService i, Logger l, IWebClient wc, IProtectionService ps)
: base(name: "Speed.cd",
description: "Your home now!",
link: "http://speed.cd/",
link: "https://speed.cd/",
caps: new TorznabCapabilities(),
manager: i,
client: wc,
logger: l,
p: ps,
configData: new ConfigurationDataBasicLogin())
configData: new ConfigurationDataBasicLogin(@"Speed.Cd have increased their security. If you are having problems please check the security tab in your Speed.Cd profile.
eg. Geo Locking, your seedbox may be in a different country to the one where you login via your web browser"))
{
AddCategoryMapping("1", TorznabCatType.MoviesOther);
AddCategoryMapping("42", TorznabCatType.Movies);
@@ -71,33 +71,46 @@ namespace Jackett.Indexers
public async Task<IndexerConfigurationStatus> ApplyConfiguration(JToken configJson)
{
configData.LoadValuesFromJson(configJson);
await DoLogin();
return IndexerConfigurationStatus.RequiresTesting;
}
private async Task DoLogin()
{
var pairs = new Dictionary<string, string> {
{ "username", configData.Username.Value },
{ "password", configData.Password.Value },
};
var result = await RequestLoginAndFollowRedirect(LoginUrl, pairs, null, true, null, SiteLink);
await ConfigureIfOK(result.Cookies, result.Content != null && result.Content.Contains("logout.php"), () =>
await ConfigureIfOK(result.Cookies, result.Content != null && result.Content.Contains("/browse.php"), () =>
{
CQ dom = result.Content;
var errorMessage = dom["h5"].First().Text().Trim();
var errorMessage = dom.Text();
throw new ExceptionWithConfigData(errorMessage, configData);
});
return IndexerConfigurationStatus.RequiresTesting;
}
public async Task<IEnumerable<ReleaseInfo>> PerformQuery(TorznabQuery query)
{
var loggedInCheck = await RequestStringWithCookies(SearchUrl);
if (!loggedInCheck.Content.Contains("/logout.php"))
{
//Cookie appears to expire after a period of time or logging in to the site via browser
await DoLogin();
}
var releases = new List<ReleaseInfo>();
Dictionary<string, string> qParams = new Dictionary<string, string>();
qParams.Add("jxt", "4");
qParams.Add("jxw", "b");
NameValueCollection qParams = new NameValueCollection();
if (!string.IsNullOrEmpty(query.SanitizedSearchTerm))
if (!string.IsNullOrEmpty(query.GetQueryString()))
{
qParams.Add("search", query.SanitizedSearchTerm);
qParams.Add("search", query.GetQueryString());
}
List<string> catList = MapTorznabCapsToTrackers(query);
@@ -105,40 +118,58 @@ namespace Jackett.Indexers
{
qParams.Add("c" + cat, "1");
}
var response = await PostDataWithCookiesAndRetry(SearchUrl, qParams);
string urlSearch = SearchUrl;
if (qParams.Count > 0)
{
urlSearch += $"?{qParams.GetQueryString()}";
}
var response = await RequestStringWithCookiesAndRetry(urlSearch);
try
{
var jsonResult = JObject.Parse(response.Content);
var resultArray = ((JArray)jsonResult["Fs"])[0]["Cn"]["torrents"];
foreach (var jobj in resultArray)
CQ dom = response.Content;
var rows = dom["div[id='torrentTable'] > div[class='box torrentBox'] > div[class='boxContent'] > table > tbody > tr"];
foreach (IDomObject row in rows)
{
CQ torrentData = row.OuterHTML;
CQ cells = row.Cq().Find("td");
string title = torrentData.Find("a[class='torrent']").First().Text().Trim();
Uri link = new Uri(SiteLink + torrentData.Find("img[class='icos save']").First().Parent().Attr("href").Trim());
Uri guid = new Uri(SiteLink + torrentData.Find("a[class='torrent']").First().Attr("href").Trim().TrimStart('/'));
long size = ReleaseInfo.GetBytes(cells.Elements.ElementAt(4).Cq().Text());
int seeders = ParseUtil.CoerceInt(cells.Elements.ElementAt(5).Cq().Text());
int leechers = ParseUtil.CoerceInt(cells.Elements.ElementAt(6).Cq().Text());
string pubDateStr = torrentData.Find("span[class^='elapsedDate']").First().Attr("title").Trim().Replace(" at", "");
DateTime publishDate = DateTime.ParseExact(pubDateStr, "dddd, MMMM d, yyyy h:mmtt", CultureInfo.InvariantCulture, DateTimeStyles.AssumeUniversal).ToLocalTime();
long category = 0;
string cat = torrentData.Find("a[class='cat']").First().Attr("id").Trim();
long.TryParse(cat, out category);
var release = new ReleaseInfo();
var id = (int)jobj["id"];
release.Comments = new Uri(string.Format(CommentsUrl, id));
release.Guid = release.Comments;
release.Link = new Uri(string.Format(DownloadUrl, id));
release.Title = Regex.Replace((string)jobj["name"], "<.*?>", String.Empty);
var SizeStr = ((string)jobj["size"]);
release.Size = ReleaseInfo.GetBytes(SizeStr);
var cat = (int)jobj["cat"];
release.Category = MapTrackerCatToNewznab(cat.ToString());
release.Seeders = ParseUtil.CoerceInt((string)jobj["seed"]);
release.Peers = ParseUtil.CoerceInt((string)jobj["leech"]) + release.Seeders;
// ex: Tuesday, May 26, 2015 at 6:00pm
var dateStr = new Regex("title=\"(.*?)\"").Match((string)jobj["added"]).Groups[1].ToString();
dateStr = dateStr.Replace(" at", "");
var dateTime = DateTime.ParseExact(dateStr, "dddd, MMMM d, yyyy h:mmtt", CultureInfo.InvariantCulture, DateTimeStyles.AssumeUniversal);
release.PublishDate = dateTime.ToLocalTime();
release.Title = title;
release.Guid = guid;
release.Link = link;
release.PublishDate = publishDate;
release.Size = size;
release.Description = release.Title;
release.Seeders = seeders;
release.Peers = seeders + leechers;
release.MinimumRatio = 1;
release.MinimumSeedTime = 172800;
release.Category = MapTrackerCatToNewznab(category.ToString());
release.Comments = guid;
releases.Add(release);
}
}
catch (Exception ex)
{

View File

@@ -39,7 +39,7 @@ namespace Jackett.Indexers
public TVChaosUK(IIndexerManagerService i, IWebClient wc, Logger l, IProtectionService ps)
: base(name: "TV Chaos",
description: "Total Chaos",
link: "https://tvchaosuk.com/",
link: "https://www.tvchaosuk.com/",
caps: TorznabUtil.CreateDefaultTorznabTVCaps(),
manager: i,
client: wc,

View File

@@ -1,5 +1,4 @@
using CsQuery;
using Jackett.Indexers;
using Jackett.Models;
using Jackett.Services;
using Jackett.Utils;
@@ -41,27 +40,35 @@ namespace Jackett.Indexers
Separate options with a space if using more than one option.<br>Filter options available:
<br><code>QualityEncodeOnly</code><br><code>FreeLeechOnly</code>"))
{
AddCategoryMapping(7, TorznabCatType.Movies);
AddCategoryMapping(7, TorznabCatType.MoviesForeign);
AddCategoryMapping(7, TorznabCatType.MoviesOther);
AddCategoryMapping(7, TorznabCatType.MoviesSD);
AddCategoryMapping(7, TorznabCatType.MoviesHD);
AddCategoryMapping(7, TorznabCatType.Movies3D);
AddCategoryMapping(7, TorznabCatType.MoviesBluRay);
AddCategoryMapping(7, TorznabCatType.MoviesDVD);
AddCategoryMapping(7, TorznabCatType.MoviesWEBDL);
AddCategoryMapping(1, TorznabCatType.Movies);
AddCategoryMapping(1, TorznabCatType.MoviesForeign);
AddCategoryMapping(1, TorznabCatType.MoviesOther);
AddCategoryMapping(1, TorznabCatType.MoviesSD);
AddCategoryMapping(1, TorznabCatType.MoviesHD);
AddCategoryMapping(1, TorznabCatType.Movies3D);
AddCategoryMapping(1, TorznabCatType.MoviesBluRay);
AddCategoryMapping(1, TorznabCatType.MoviesDVD);
AddCategoryMapping(1, TorznabCatType.MoviesWEBDL);
}
public async Task<IndexerConfigurationStatus> ApplyConfiguration(JToken configJson)
{
configData.LoadValuesFromJson(configJson);
await DoLogin();
return IndexerConfigurationStatus.RequiresTesting;
}
private async Task DoLogin()
{
var pairs = new Dictionary<string, string> {
{ "username", configData.Username.Value },
{ "password", configData.Password.Value },
{ "keeplogged", "1" },
{ "login", "Log In!" }
};
var response = await RequestLoginAndFollowRedirect(LoginUrl, pairs, null, true, indexUrl, SiteLink);
await ConfigureIfOK(response.Cookies, response.Content != null && response.Content.Contains("/logout.php"), () =>
@@ -70,11 +77,24 @@ namespace Jackett.Indexers
string errorMessage = "Unable to login to TehConnection";
throw new ExceptionWithConfigData(errorMessage, configData);
});
return IndexerConfigurationStatus.RequiresTesting;
}
public async Task<IEnumerable<ReleaseInfo>> PerformQuery(TorznabQuery query)
{
var loggedInCheck = await RequestStringWithCookies(SearchUrl);
if (!loggedInCheck.Content.Contains("/logout.php"))
{
//Cookie appears to expire after a period of time or logging in to the site via browser
DateTime lastLoggedInCheck;
DateTime.TryParse(configData.LastLoggedInCheck.Value, out lastLoggedInCheck);
if (lastLoggedInCheck < DateTime.Now.AddMinutes(-15))
{
await DoLogin();
configData.LastLoggedInCheck.Value = DateTime.Now.ToString("o");
SaveConfig();
}
}
var releases = new List<ReleaseInfo>();
bool configFreeLeechOnly = configData.FilterString.Value.ToLowerInvariant().Contains("freeleechonly");
bool configQualityEncodeOnly = configData.FilterString.Value.ToLowerInvariant().Contains("qualityencodeonly");
@@ -84,7 +104,14 @@ namespace Jackett.Indexers
movieListSearchUrl = SearchUrl;
else
{
movieListSearchUrl = string.Format("{0}?action=basic&searchstr={1}", SearchUrl, HttpUtility.UrlEncode(query.GetQueryString()));
if (!string.IsNullOrEmpty(query.ImdbID))
{
movieListSearchUrl = string.Format("{0}?action=basic&searchstr={1}", SearchUrl, HttpUtility.UrlEncode(query.ImdbID));
}
else
{
movieListSearchUrl = string.Format("{0}?action=basic&searchstr={1}", SearchUrl, HttpUtility.UrlEncode(query.GetQueryString()));
}
}
var results = await RequestStringWithCookiesAndRetry(movieListSearchUrl);

View File

@@ -152,15 +152,18 @@ namespace Jackett.Indexers
break;
}
var cat = row.Cq().Find("td:eq(0) a").First().Attr("href").Substring(15);
release.Category = MapTrackerCatToNewznab(cat);
// Check if the release has been assigned a category
if (row.Cq().Find("td:eq(0) a").Length > 0)
{
var cat = row.Cq().Find("td:eq(0) a").First().Attr("href").Substring(15);
release.Category = MapTrackerCatToNewznab(cat);
}
var qLink = row.Cq().Find("td:eq(1) a").First();
release.Link = new Uri(SiteLink + qLink.Attr("href"));
var added = row.Cq().Find("td:eq(4)").First().Text().Trim();
release.PublishDate = DateTimeUtil.FromTimeAgo(added);
release.PublishDate = DateTime.ParseExact(added, "yyyy-MM-ddHH:mm:ss", CultureInfo.InvariantCulture, DateTimeStyles.AssumeUniversal).ToLocalTime();
var sizeStr = row.Cq().Find("td:eq(6)").First().Text().Trim();
release.Size = ReleaseInfo.GetBytes(sizeStr);

View File

@@ -61,9 +61,12 @@ namespace Jackett.Indexers
AddCategoryMapping(44, TorznabCatType.MoviesSD);
AddCategoryMapping(1, TorznabCatType.MoviesSD);
// Music foreign
// Music packs
// Music videos
// Music
AddCategoryMapping(17, TorznabCatType.AudioMP3);
AddCategoryMapping(44, TorznabCatType.AudioLossless);
AddCategoryMapping(23, TorznabCatType.AudioForeign);
AddCategoryMapping(41, TorznabCatType.AudioOther);
AddCategoryMapping(16, TorznabCatType.AudioVideo);
AddCategoryMapping(4, TorznabCatType.PCGames);
// ps3

View File

@@ -82,6 +82,12 @@ namespace Jackett.Indexers
public async Task<IndexerConfigurationStatus> ApplyConfiguration(JToken configJson)
{
configData.LoadValuesFromJson(configJson);
await DoLogin();
return IndexerConfigurationStatus.RequiresTesting;
}
private async Task DoLogin()
{
var pairs = new Dictionary<string, string> {
{ "username", configData.Username.Value },
{ "password", configData.Password.Value },
@@ -97,11 +103,17 @@ namespace Jackett.Indexers
var errorMessage = messageEl.Text().Trim();
throw new ExceptionWithConfigData(errorMessage, configData);
});
return IndexerConfigurationStatus.RequiresTesting;
}
public async Task<IEnumerable<ReleaseInfo>> PerformQuery(TorznabQuery query)
{
var loggedInCheck = await RequestStringWithCookies(SearchUrl);
if (!loggedInCheck.Content.Contains("/logout.php"))
{
//Cookie appears to expire after a period of time or logging in to the site via browser
await DoLogin();
}
var releases = new List<ReleaseInfo>();
var searchString = query.GetQueryString();
var searchUrl = SearchUrl;
@@ -151,6 +163,10 @@ namespace Jackett.Indexers
release.Link = new Uri(SiteLink + qRow.Find(".quickdownload > a").Attr("href").Substring(1));
var dateString = qRow.Find(".name")[0].InnerText.Trim().Replace(" ", string.Empty).Replace("Addedinon", string.Empty);
//Fix for issue 2016-04-30
dateString = dateString.Replace("\r", "").Replace("\n", "");
//"2015-04-25 23:38:12"
//"yyyy-MMM-dd hh:mm:ss"
release.PublishDate = DateTime.ParseExact(dateString, "yyyy-MM-ddHH:mm:ss", CultureInfo.InvariantCulture);

View File

@@ -0,0 +1,148 @@
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.Globalization;
using System.Threading.Tasks;
using System.Web;
using Jackett.Models.IndexerConfig;
using AngleSharp.Parser.Html;
using System.Text.RegularExpressions;
namespace Jackett.Indexers
{
public class TransmitheNet : BaseIndexer, IIndexer
{
private string LoginUrl { get { return SiteLink + "login.php"; } }
private string SearchUrl { get { return SiteLink + "torrents.php?action=basic&order_by=time&order_way=desc&search_type=0&taglist=&tags_type=0"; } }
new ConfigurationDataBasicLogin configData
{
get { return (ConfigurationDataBasicLogin)base.configData; }
set { base.configData = value; }
}
public TransmitheNet(IIndexerManagerService i, Logger l, IWebClient c, IProtectionService ps)
: base(name: "TransmitTheNet",
description: " At Transmithe.net we will change the way you think about TV",
link: "https://transmithe.net/",
caps: TorznabUtil.CreateDefaultTorznabTVCaps(),
manager: i,
client: c,
logger: l,
p: ps,
configData: new ConfigurationDataBasicLogin("For best results, change the 'Torrents per page' setting to 100 in your profile on the TTN webpage."))
{
}
public async Task<IndexerConfigurationStatus> ApplyConfiguration(JToken configJson)
{
configData.LoadValuesFromJson(configJson);
await DoLogin();
return IndexerConfigurationStatus.RequiresTesting;
}
private async Task DoLogin()
{
var pairs = new Dictionary<string, string> {
{ "username", configData.Username.Value },
{ "password", configData.Password.Value },
{ "keeplogged", "on" },
{ "login", "Login" }
};
CookieHeader = string.Empty;
var response = await RequestLoginAndFollowRedirect(LoginUrl, pairs, CookieHeader, true, null, LoginUrl);
await ConfigureIfOK(response.Cookies, response.Content != null && response.Content.Contains("logout.php"), () =>
{
var parser = new HtmlParser();
var document = parser.Parse(response.Content);
var messageEl = document.QuerySelector("form > span[class='warning']");
var errorMessage = messageEl.TextContent.Trim();
throw new ExceptionWithConfigData(errorMessage, configData);
});
}
public async Task<IEnumerable<ReleaseInfo>> PerformQuery(TorznabQuery query)
{
var loggedInCheck = await RequestStringWithCookies(SearchUrl);
if (!loggedInCheck.Content.Contains("logout.php"))
{
//Cookie appears to expire after a period of time or logging in to the site via browser
await DoLogin();
}
string Url;
if (string.IsNullOrEmpty(query.GetQueryString()))
Url = SearchUrl;
else
{
Url = $"{SearchUrl}&searchtext={HttpUtility.UrlEncode(query.GetQueryString())}";
}
var response = await RequestStringWithCookiesAndRetry(Url);
List<ReleaseInfo> releases = ParseResponse(response.Content);
return releases;
}
public List<ReleaseInfo> ParseResponse(string htmlResponse)
{
List<ReleaseInfo> releases = new List<ReleaseInfo>();
try
{
var parser = new HtmlParser();
var document = parser.Parse(htmlResponse);
var rows = document.QuerySelectorAll(".torrent_table > tbody > tr:not(:First-child)");
foreach (var row in rows)
{
var release = new ReleaseInfo();
string title = row.QuerySelector("a[data-src]").GetAttribute("data-src");
if (string.IsNullOrEmpty(title) || title == "0")
{
title = row.QuerySelector("a[data-src]").TextContent;
title = Regex.Replace(title, @"[\[\]\/]", "");
}
else
{
if (title.Length > 5 && title.Substring(title.Length - 5).Contains("."))
{
title = title.Remove(title.LastIndexOf("."));
}
}
release.Title = title;
release.Description = release.Title;
release.Guid = new Uri(SiteLink + row.QuerySelector("a[data-src]").GetAttribute("href"));
release.Comments = release.Guid;
release.Link = new Uri(SiteLink + row.QuerySelector("a[href*='action=download']").GetAttribute("href"));
release.Category = TvCategoryParser.ParseTvShowQuality(release.Title);
var timeAnchor = row.QuerySelector("span[class='time']");
release.PublishDate = DateTime.ParseExact(timeAnchor.GetAttribute("title"), "MMM dd yyyy, HH:mm", CultureInfo.InvariantCulture, DateTimeStyles.AssumeLocal);
release.Seeders = ParseUtil.CoerceInt(timeAnchor.ParentElement.NextElementSibling.NextElementSibling.TextContent.Trim());
release.Peers = ParseUtil.CoerceInt(timeAnchor.ParentElement.NextElementSibling.NextElementSibling.NextElementSibling.TextContent.Trim()) + release.Seeders;
release.Size = ReleaseInfo.GetBytes(timeAnchor.ParentElement.PreviousElementSibling.TextContent);
release.MinimumRatio = 1;
release.MinimumSeedTime = 172800;
releases.Add(release);
}
}
catch (Exception ex)
{
OnParseError(htmlResponse, ex);
}
return releases;
}
}
}

View File

@@ -0,0 +1,945 @@
using System;
using System.Collections.Generic;
using System.Collections.Specialized;
using System.Linq;
using System.Reflection;
using System.Text.RegularExpressions;
using System.Threading.Tasks;
using CsQuery;
using Jackett.Models;
using Jackett.Models.IndexerConfig.Bespoke;
using Jackett.Services;
using Jackett.Utils;
using Jackett.Utils.Clients;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using NLog;
namespace Jackett.Indexers
{
/// <summary>
/// Provider for WiHD Private French Tracker
/// </summary>
public class WiHD : BaseIndexer, IIndexer
{
private string LoginUrl { get { return SiteLink + "login"; } }
private string LoginCheckUrl { get { return SiteLink + "login_check"; } }
private string SearchUrl { get { return SiteLink + "torrent/ajaxfiltertorrent/"; } }
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 Dictionary<string, string> emulatedBrowserHeaders = new Dictionary<string, string>();
private CQ fDom = null;
private ConfigurationDataWiHD ConfigData
{
get { return (ConfigurationDataWiHD)configData; }
set { base.configData = value; }
}
public WiHD(IIndexerManagerService i, IWebClient w, Logger l, IProtectionService ps)
: base(
name: "WiHD",
description: "Your World in High Definition",
link: "http://world-in-hd.net/",
caps: new TorznabCapabilities(),
manager: i,
client: w,
logger: l,
p: ps,
downloadBase: "http://world-in-hd.net/torrents/download/",
configData: new ConfigurationDataWiHD())
{
// Clean capabilities
TorznabCaps.Categories.Clear();
// Movies
AddCategoryMapping("565af82b1fd35761568b4572", TorznabCatType.MoviesHD); // 1080P
AddCategoryMapping("565af82b1fd35761568b4574", TorznabCatType.MoviesHD); // 720P
AddCategoryMapping("565af82b1fd35761568b4576", TorznabCatType.MoviesHD); // HDTV
AddCategoryMapping("565af82b1fd35761568b4578", TorznabCatType.MoviesBluRay); // Bluray
AddCategoryMapping("565af82b1fd35761568b457a", TorznabCatType.MoviesBluRay); // Bluray Remux
AddCategoryMapping("565af82b1fd35761568b457c", TorznabCatType.Movies3D); // Bluray 3D
// TV
AddCategoryMapping("565af82d1fd35761568b4587", TorznabCatType.TVHD); // 1080P
AddCategoryMapping("565af82d1fd35761568b4589", TorznabCatType.TVHD); // 720P
AddCategoryMapping("565af82d1fd35761568b458b", TorznabCatType.TVHD); // HDTV
AddCategoryMapping("565af82d1fd35761568b458d", TorznabCatType.TVHD); // Bluray
AddCategoryMapping("565af82d1fd35761568b458f", TorznabCatType.TVHD); // Bluray Remux
AddCategoryMapping("565af82d1fd35761568b4591", TorznabCatType.TVHD); // Bluray 3D
// Anime
AddCategoryMapping("565af82d1fd35761568b459c", TorznabCatType.TVAnime); // 1080P
AddCategoryMapping("565af82d1fd35761568b459e", TorznabCatType.TVAnime); // 720P
AddCategoryMapping("565af82d1fd35761568b45a0", TorznabCatType.TVAnime); // HDTV
AddCategoryMapping("565af82d1fd35761568b45a2", TorznabCatType.TVAnime); // Bluray
AddCategoryMapping("565af82d1fd35761568b45a4", TorznabCatType.TVAnime); // Bluray Remux
AddCategoryMapping("565af82d1fd35761568b45a6", TorznabCatType.TVAnime); // Bluray 3D
// Other
AddCategoryMapping("565af82d1fd35761568b45af", TorznabCatType.PC); // Apps
AddCategoryMapping("565af82d1fd35761568b45b1", TorznabCatType.AudioVideo); // Clips
AddCategoryMapping("565af82d1fd35761568b45b3", TorznabCatType.AudioOther); // Audios Tracks of Movies/TV/Anime
AddCategoryMapping("565af82d1fd35761568b45b5", TorznabCatType.TVDocumentary); // Documentary
AddCategoryMapping("565af82d1fd35761568b45b7", TorznabCatType.MoviesBluRay); // Bluray (ALL)
}
/// <summary>
/// Configure our WiHD Provider
/// </summary>
/// <param name="configJson">Our params in Json</param>
/// <returns>Configuration state</returns>
public async Task<IndexerConfigurationStatus> ApplyConfiguration(JToken configJson)
{
// Retrieve config values set by Jackett's user
ConfigData.LoadValuesFromJson(configJson);
// Check & Validate Config
validateConfig();
// Setting our data for a better emulated browser (maximum security)
// TODO: Encoded Content not supported by Jackett at this time
// emulatedBrowserHeaders.Add("Accept-Encoding", "gzip, deflate");
// If we want to simulate a browser
if (ConfigData.Browser.Value) {
// Clean headers
emulatedBrowserHeaders.Clear();
// Inject headers
emulatedBrowserHeaders.Add("Accept", ConfigData.HeaderAccept.Value);
emulatedBrowserHeaders.Add("Accept-Language", ConfigData.HeaderAcceptLang.Value);
emulatedBrowserHeaders.Add("DNT", Convert.ToInt32(ConfigData.HeaderDNT.Value).ToString());
emulatedBrowserHeaders.Add("Upgrade-Insecure-Requests", Convert.ToInt32(ConfigData.HeaderUpgradeInsecure.Value).ToString());
emulatedBrowserHeaders.Add("User-Agent", ConfigData.HeaderUserAgent.Value);
}
// Getting login form to retrieve CSRF token
var myRequest = new Utils.Clients.WebRequest()
{
Url = LoginUrl
};
// Add our headers to request
myRequest.Headers = emulatedBrowserHeaders;
// Get login page
var loginPage = await webclient.GetString(myRequest);
// Retrieving our CSRF token
CQ loginPageDom = loginPage.Content;
var csrfToken = loginPageDom["input[name=\"_csrf_token\"]"].Last();
// Building login form data
var pairs = new Dictionary<string, string> {
{ "_csrf_token", csrfToken.Attr("value") },
{ "_username", ConfigData.Username.Value },
{ "_password", ConfigData.Password.Value },
{ "_remember_me", "on" },
{ "_submit", "" }
};
// Do the login
var request = new Utils.Clients.WebRequest(){
Cookies = loginPage.Cookies,
PostData = pairs,
Referer = LoginUrl,
Type = RequestType.POST,
Url = LoginUrl,
Headers = emulatedBrowserHeaders
};
// Perform loggin
latencyNow();
output("\nPerform loggin.. with " + LoginCheckUrl);
var response = await RequestLoginAndFollowRedirect(LoginCheckUrl, pairs, loginPage.Cookies, true, null, null);
// Test if we are logged in
await ConfigureIfOK(response.Cookies, response.Content != null && response.Content.Contains("/logout"), () =>
{
// Oops, unable to login
output("-> Login failed", "error");
throw new ExceptionWithConfigData("Failed to login", configData);
});
output("-> Login Success");
return IndexerConfigurationStatus.RequiresTesting;
}
/// <summary>
/// Execute our search query
/// </summary>
/// <param name="query">Query</param>
/// <returns>Releases</returns>
public async Task<IEnumerable<ReleaseInfo>> PerformQuery(TorznabQuery query)
{
var releases = new List<ReleaseInfo>();
var torrentRowList = new List<CQ>();
var searchTerm = query.GetQueryString();
var searchUrl = SearchUrl;
int nbResults = 0;
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))
{
lock (cache)
{
// Remove old cache items
CleanCache();
// Search in cache
var cachedResult = cache.Where(i => i.Query == searchTerm).FirstOrDefault();
if (cachedResult != null)
return cachedResult.Results.Select(s => (ReleaseInfo)s.Clone()).ToArray();
}
}
// Add emulated XHR request
emulatedBrowserHeaders.Add("X-Requested-With", "XMLHttpRequest");
// Build our query
var request = buildQuery(searchTerm, query, searchUrl);
// Getting results & Store content
WebClientStringResult results = await queryExec(request);
fDom = results.Content;
try
{
// Find number of results
nbResults = ParseUtil.CoerceInt(Regex.Match(fDom["div.ajaxtotaltorrentcount"].Text(), @"\d+").Value);
// Find torrent rows
var firstPageRows = findTorrentRows();
// Add them to torrents list
torrentRowList.AddRange(firstPageRows.Select(fRow => fRow.Cq()));
// Check if there are pagination links at bottom
Boolean pagination = (nbResults != 0);
// If pagination available
if (pagination)
{
// Calculate numbers of pages available for this search query (Based on number results and number of torrents on first page)
pageLinkCount = (int)Math.Ceiling((double)nbResults / firstPageRows.Length);
}
else {
// Check if we have a minimum of one result
if (firstPageRows.Length >= 1)
{
// Set page count arbitrary to one
pageLinkCount = 1;
}
else
{
output("\nNo result found for your query, please try another search term ...\n", "info");
// No result found for this query
return releases;
}
}
output("\nFound " + nbResults + " result(s) in " + pageLinkCount + " page(s) for this query !");
output("\nThere are " + firstPageRows.Length + " results on the first page !");
// If we have a term used for search and pagination result superior to one
if (!string.IsNullOrWhiteSpace(query.GetQueryString()) && pageLinkCount > 1)
{
// Starting with page #2
for (int i = 2; i <= Math.Min(Int32.Parse(ConfigData.Pages.Value), pageLinkCount); i++)
{
output("\nProcessing page #" + i);
// Request our page
latencyNow();
// Build our query
var pageRequest = buildQuery(searchTerm, query, searchUrl, i);
// Getting results & Store content
WebClientStringResult pageResults = await queryExec(pageRequest);
// Assign response
fDom = pageResults.Content;
// Process page results
var additionalPageRows = findTorrentRows();
// Add them to torrents list
torrentRowList.AddRange(additionalPageRows.Select(fRow => fRow.Cq()));
}
}
// Loop on results
foreach (CQ tRow in torrentRowList)
{
output("\n=>> Torrent #" + (releases.Count + 1));
// Release Name
string name = tRow.Find(".torrent-h3 > h3 > a").Attr("title").ToString();
output("Release: " + name);
// Category
string categoryID = tRow.Find(".category > img").Attr("src").Split('/').Last().ToString();
string categoryName = tRow.Find(".category > img").Attr("title").ToString();
output("Category: " + MapTrackerCatToNewznab(mediaToCategory(categoryID, categoryName)) + " (" + categoryName + ")");
// Uploader
string uploader = tRow.Find(".uploader > span > a").Attr("title").ToString();
output("Uploader: " + uploader);
// Seeders
int seeders = ParseUtil.CoerceInt(Regex.Match(tRow.Find(".seeders")[0].LastChild.ToString(), @"\d+").Value);
output("Seeders: " + seeders);
// Leechers
int leechers = ParseUtil.CoerceInt(Regex.Match(tRow.Find(".leechers")[0].LastChild.ToString(), @"\d+").Value);
output("Leechers: " + leechers);
// Completed
int completed = ParseUtil.CoerceInt(Regex.Match(tRow.Find(".completed")[0].LastChild.ToString(), @"\d+").Value);
output("Completed: " + completed);
// Comments
int comments = ParseUtil.CoerceInt(Regex.Match(tRow.Find(".comments")[0].LastChild.ToString(), @"\d+").Value);
output("Comments: " + comments);
// Size & Publish Date
string infosData = tRow.Find(".torrent-h3 > span")[0].LastChild.ToString().Trim();
IList<string> infosList = infosData.Split('-').Select(s => s.Trim()).Where(s => s != String.Empty).ToList();
// --> Size
var size = ReleaseInfo.GetBytes(infosList[1].Replace("Go", "gb").Replace("Mo", "mb").Replace("Ko", "kb"));
output("Size: " + infosList[1] + " (" + size + " bytes)");
// --> Publish Date
IList<string> clockList = infosList[0].Replace("Il y a", "").Split(',').Select(s => s.Trim()).Where(s => s != String.Empty).ToList();
var clock = agoToDate(clockList);
output("Released on: " + clock.ToString());
// Torrent Details URL
string details = tRow.Find(".torrent-h3 > h3 > a").Attr("href").ToString().TrimStart('/');
Uri detailsLink = new Uri(SiteLink + details);
output("Details: " + detailsLink.AbsoluteUri);
// Torrent Comments URL
Uri commentsLink = new Uri(SiteLink + details + "#tab_2");
output("Comments Link: " + commentsLink.AbsoluteUri);
// Torrent Download URL
string download = tRow.Find(".download-item > a").Attr("href").ToString().TrimStart('/');
Uri downloadLink = new Uri(SiteLink + download);
output("Download Link: " + downloadLink.AbsoluteUri);
// Building release infos
var release = new ReleaseInfo();
release.Category = MapTrackerCatToNewznab(mediaToCategory(categoryID, categoryName));
release.Title = name;
release.Seeders = seeders;
release.Peers = seeders + leechers;
release.MinimumRatio = 1;
release.MinimumSeedTime = 345600;
release.PublishDate = clock;
release.Size = size;
release.Guid = detailsLink;
release.Comments = commentsLink;
release.Link = downloadLink;
releases.Add(release);
}
}
catch (Exception ex)
{
OnParseError("Error, unable to parse result \n" + ex.StackTrace, ex);
}
finally
{
// Remove our XHR request header
emulatedBrowserHeaders.Remove("X-Requested-With");
}
// Return found releases
return releases;
}
/// <summary>
/// Build query to process
/// </summary>
/// <param name="term">Term to search</param>
/// <param name="query">Torznab Query for categories mapping</param>
/// <param name="url">Search url for provider</param>
/// <param name="page">Page number to request</param>
/// <returns>URL to query for parsing and processing results</returns>
private string buildQuery(string term, TorznabQuery query, string url, int page = 1)
{
var parameters = new NameValueCollection();
List<string> categoriesList = MapTorznabCapsToTrackers(query);
string categories = null;
// If search term not provided
if (string.IsNullOrWhiteSpace(term))
{
// Showing all torrents (just for output function)
term = "null";
}
// Encode & Add search term to URL
url += Uri.EscapeDataString(term);
// Check if we are processing a new page
if (page > 1)
{
// Adding page number to query
url += "/" + page.ToString();
}
// Adding interrogation point
url += "?";
// Building our tracker query
parameters.Add("exclu", Convert.ToInt32(ConfigData.Exclusive.Value).ToString());
parameters.Add("freeleech", Convert.ToInt32(ConfigData.Freeleech.Value).ToString());
parameters.Add("reseed", Convert.ToInt32(ConfigData.Reseed.Value).ToString());
// Loop on Categories needed
foreach (string category in categoriesList)
{
// If last, build !
if(categoriesList.Last() == category)
{
// Adding previous categories to URL with latest category
parameters.Add(Uri.EscapeDataString("subcat[]"), category + categories);
}
else
{
// Build categories parameter
categories += "&" + Uri.EscapeDataString("subcat[]") + "=" + category;
}
}
// Add timestamp as a query param (for no caching)
parameters.Add("_", UnixTimeNow().ToString());
// Building our query -- Cannot use GetQueryString due to UrlEncode (generating wrong subcat[] param)
url += string.Join("&", parameters.AllKeys.Select(a => a + "=" + parameters[a]));
output("\nBuilded query for \"" + term + "\"... " + url);
// Return our search url
return url;
}
/// <summary>
/// Switch Method for Querying
/// </summary>
/// <param name="request">URL created by Query Builder</param>
/// <returns>Results from query</returns>
private async Task<WebClientStringResult> queryExec(string request)
{
WebClientStringResult results = null;
// Switch in we are in DEV mode with Hard Drive Cache or not
if (DevMode && CacheMode)
{
// Check Cache before querying and load previous results if available
results = await queryCache(request);
}
else
{
// Querying tracker directly
results = await queryTracker(request);
}
return results;
}
/// <summary>
/// Get Torrents Page from Cache by Query Provided
/// </summary>
/// <param name="request">URL created by Query Builder</param>
/// <returns>Results from query</returns>
private async Task<WebClientStringResult> queryCache(string request)
{
WebClientStringResult results = null;
// Create Directory if not exist
System.IO.Directory.CreateDirectory(directory);
// Clean Storage Provider Directory from outdated cached queries
cleanCacheStorage();
// Remove timestamp from query
string file = string.Join("&", Regex.Split(request, "&").Where(s => !Regex.IsMatch(s, @"_=\d")).ToList());
// Create fingerprint for request
file = directory + file.GetHashCode() + ".json";
// Checking modes states
if (System.IO.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));
}
else
{
// No cached file found, querying tracker directly
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));
}
return results;
}
/// <summary>
/// Get Torrents Page from Tracker by Query Provided
/// </summary>
/// <param name="request">URL created by Query Builder</param>
/// <returns>Results from query</returns>
private async Task<WebClientStringResult> queryTracker(string request)
{
WebClientStringResult results = null;
// Cache mode not enabled or cached file didn't exist for our query
output("\nQuerying tracker for results....");
// Request our first page
latencyNow();
results = await RequestStringWithCookiesAndRetry(request, null, null, emulatedBrowserHeaders);
// Return results from tracker
return results;
}
/// <summary>
/// Clean Hard Drive Cache Storage
/// </summary>
/// <param name="force">Force Provider Folder deletion</param>
private void cleanCacheStorage(Boolean force = false)
{
// Check cleaning method
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))
{
// Delete storage directory of provider
System.IO.Directory.Delete(directory, true);
output("-> Storage folder deleted successfully.");
}
else
{
// No directory, so nothing to do
output("-> No Storage folder found for this provider !");
}
}
else
{
int 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))
.Where(f => f.LastAccessTime < DateTime.Now.AddMilliseconds(-Convert.ToInt32(ConfigData.HardDriveCacheKeepTime.Value)))
.ToList()
.ForEach(f => {
output("Deleting cached file << " + f.Name + " >> ... done.");
f.Delete();
i++;
});
// Inform on what was cleaned during process
if (i > 0)
{
output("-> Deleted " + i + " cached files during cleaning.");
}
else {
output("-> Nothing deleted during cleaning.");
}
}
}
/// <summary>
/// Generate a random fake latency to avoid detection on tracker side
/// </summary>
private void latencyNow()
{
// Need latency ?
if(Latency)
{
// Generate a random value in our range
var random = new Random(DateTime.Now.Millisecond);
int waiting = random.Next(Convert.ToInt32(ConfigData.LatencyStart.Value), Convert.ToInt32(ConfigData.LatencyEnd.Value));
output("\nLatency Faker => Sleeping for " + waiting + " ms...");
// Sleep now...
System.Threading.Thread.Sleep(waiting);
}
}
/// <summary>
/// Generate an UTC Unix TimeStamp
/// </summary>
/// <returns>Unix TimeStamp</returns>
private long UnixTimeNow()
{
var timeSpan = (DateTime.UtcNow - new DateTime(1970, 1, 1, 0, 0, 0));
return (long)timeSpan.TotalSeconds;
}
/// <summary>
/// Find torrent rows in search pages
/// </summary>
/// <returns>JQuery Object</returns>
private CQ findTorrentRows()
{
// Return all occurencis of torrents found
return fDom[".torrent-item"];
}
/// <summary>
/// Convert Ago date to DateTime
/// </summary>
/// <param name="clockList"></param>
/// <returns>A DateTime</returns>
private DateTime agoToDate(IList<string> clockList)
{
DateTime release = DateTime.Now;
foreach(var ago in clockList)
{
// Check for years
if(ago.Contains("Années") || ago.Contains("Année"))
{
// Number of years to remove
int years = ParseUtil.CoerceInt(Regex.Match(ago.ToString(), @"\d+").Value);
// Removing
release = release.AddYears(-years);
continue;
}
// Check for months
else if (ago.Contains("Mois"))
{
// Number of months to remove
int months = ParseUtil.CoerceInt(Regex.Match(ago.ToString(), @"\d+").Value);
// Removing
release = release.AddMonths(-months);
continue;
}
// Check for days
else if (ago.Contains("Jours") || ago.Contains("Jour"))
{
// Number of days to remove
int days = ParseUtil.CoerceInt(Regex.Match(ago.ToString(), @"\d+").Value);
// Removing
release = release.AddDays(-days);
continue;
}
// Check for hours
else if (ago.Contains("Heures") || ago.Contains("Heure"))
{
// Number of hours to remove
int hours = ParseUtil.CoerceInt(Regex.Match(ago.ToString(), @"\d+").Value);
// Removing
release = release.AddHours(-hours);
continue;
}
// Check for minutes
else if (ago.Contains("Minutes") || ago.Contains("Minute"))
{
// Number of minutes to remove
int minutes = ParseUtil.CoerceInt(Regex.Match(ago.ToString(), @"\d+").Value);
// Removing
release = release.AddMinutes(-minutes);
continue;
}
// Check for seconds
else if (ago.Contains("Secondes") || ago.Contains("Seconde"))
{
// Number of seconds to remove
int seconds = ParseUtil.CoerceInt(Regex.Match(ago.ToString(), @"\d+").Value);
// Removing
release = release.AddSeconds(-seconds);
continue;
}
else
{
output("Unable to detect release date of torrent", "error");
//throw new Exception("Unable to detect release date of torrent");
}
}
return release;
}
/// <summary>
/// Retrieve category ID from media ID
/// </summary>
/// <param name="media">Media ID</param>
/// <returns>Category ID</returns>
private string mediaToCategory(string media, string name)
{
// Declare our Dictionnary -- Media ID (key) <-> Category ID (value)
Dictionary<string, string> dictionary = new Dictionary<string, string>();
// Movies
dictionary.Add("565af82b1fd35761568b4573", "565af82b1fd35761568b4572"); // 1080P
dictionary.Add("565af82b1fd35761568b4575", "565af82b1fd35761568b4574"); // 720P
dictionary.Add("565af82b1fd35761568b4577", "565af82b1fd35761568b4576"); // HDTV
dictionary.Add("565af82b1fd35761568b4579", "565af82b1fd35761568b4578"); // Bluray
dictionary.Add("565af82b1fd35761568b457b", "565af82b1fd35761568b457a"); // Bluray Remux
dictionary.Add("565af82b1fd35761568b457d", "565af82b1fd35761568b457c"); // Bluray 3D
// TV
dictionary.Add("565af82d1fd35761568b4588", "565af82d1fd35761568b4587"); // 1080P
dictionary.Add("565af82d1fd35761568b458a", "565af82d1fd35761568b4589"); // 720P
dictionary.Add("565af82d1fd35761568b458c", "565af82d1fd35761568b458b"); // HDTV
dictionary.Add("565af82d1fd35761568b458e", "565af82d1fd35761568b458d"); // Bluray
dictionary.Add("565af82d1fd35761568b4590", "565af82d1fd35761568b458f"); // Bluray Remux
dictionary.Add("565af82d1fd35761568b4592", "565af82d1fd35761568b4591"); // Bluray 3D
// Anime
dictionary.Add("565af82d1fd35761568b459d", "565af82d1fd35761568b459c"); // 1080P
dictionary.Add("565af82d1fd35761568b459f", "565af82d1fd35761568b459e"); // 720P
dictionary.Add("565af82d1fd35761568b45a1", "565af82d1fd35761568b45a0"); // HDTV
dictionary.Add("565af82d1fd35761568b45a3", "565af82d1fd35761568b45a2"); // Bluray
dictionary.Add("565af82d1fd35761568b45a5", "565af82d1fd35761568b45a4"); // Bluray Remux
// BUG ~~ Media ID for Anime BR 3D is same as TV BR 3D ~~
//dictionary.Add("565af82d1fd35761568b4592", "565af82d1fd35761568b45a6"); // Bluray 3D
// Other
dictionary.Add("565af82d1fd35761568b45b0", "565af82d1fd35761568b45af"); // Apps
dictionary.Add("565af82d1fd35761568b45b2", "565af82d1fd35761568b45b1"); // Clips
dictionary.Add("565af82d1fd35761568b45b4", "565af82d1fd35761568b45b3"); // Audios Tracks of Movies/TV/Anime
dictionary.Add("565af82d1fd35761568b45b6", "565af82d1fd35761568b45b5"); // Documentary
dictionary.Add("565af82d1fd35761568b45b8", "565af82d1fd35761568b45b7"); // Bluray (ALL)
// Check if we know this media ID
if (dictionary.ContainsKey(media))
{
// Due to a bug on tracker side, check for a specific id/name as image is same for TV/Anime BR 3D
if(media == "565af82d1fd35761568b4592" && name == "Animations - Bluray 3D")
{
// If it's an Anime BR 3D
return "565af82d1fd35761568b45a6";
}
else
{
// Return category ID for media ID
return dictionary[media];
}
}
else
{
// Media ID unknown
throw new Exception("Media ID Unknow !");
}
}
/// <summary>
/// Output message for logging or developpment (console)
/// </summary>
/// <param name="message">Message to output</param>
/// <param name="level">Level for Logger</param>
private void output(string message, string level = "debug")
{
// Check if we are in dev mode
if(DevMode)
{
// Output message to console
Console.WriteLine(message);
}
else
{
// Send message to logger with level
switch (level)
{
default:
goto case "debug";
case "debug":
// Only if Debug Level Enabled on Jackett
if(Engine.Logger.IsDebugEnabled)
{
logger.Debug(message);
}
break;
case "info":
logger.Info(message);
break;
case "error":
logger.Error(message);
break;
}
}
}
/// <summary>
/// Validate Config entered by user on Jackett
/// </summary>
private void validateConfig()
{
output("\nValidating Settings ... \n");
// Check Username Setting
if (string.IsNullOrEmpty(ConfigData.Username.Value))
{
throw new ExceptionWithConfigData("You must provide a username for this tracker to login !", ConfigData);
}
else
{
output("Validated Setting -- Username (auth) => " + ConfigData.Username.Value.ToString());
}
// Check Password Setting
if (string.IsNullOrEmpty(ConfigData.Password.Value))
{
throw new ExceptionWithConfigData("You must provide a password with your username for this tracker to login !", ConfigData);
}
else
{
output("Validated Setting -- Password (auth) => " + ConfigData.Password.Value.ToString());
}
// Check Max Page Setting
if (!string.IsNullOrEmpty(ConfigData.Pages.Value))
{
try
{
output("Validated Setting -- Max Pages => " + Convert.ToInt32(ConfigData.Pages.Value));
}
catch (Exception)
{
throw new ExceptionWithConfigData("Please enter a numeric maximum number of pages to crawl !", ConfigData);
}
}
else
{
throw new ExceptionWithConfigData("Please enter a maximum number of pages to crawl !", ConfigData);
}
// Check Latency Setting
if (ConfigData.Latency.Value)
{
output("\nValidated Setting -- Latency Simulation enabled");
// Check Latency Start Setting
if (!string.IsNullOrEmpty(ConfigData.LatencyStart.Value))
{
try
{
output("Validated Setting -- Latency Start => " + Convert.ToInt32(ConfigData.LatencyStart.Value));
}
catch (Exception)
{
throw new ExceptionWithConfigData("Please enter a numeric latency start in ms !", ConfigData);
}
}
else
{
throw new ExceptionWithConfigData("Latency Simulation enabled, Please enter a start latency !", ConfigData);
}
// Check Latency End Setting
if (!string.IsNullOrEmpty(ConfigData.LatencyEnd.Value))
{
try
{
output("Validated Setting -- Latency End => " + Convert.ToInt32(ConfigData.LatencyEnd.Value));
}
catch (Exception)
{
throw new ExceptionWithConfigData("Please enter a numeric latency end in ms !", ConfigData);
}
}
else
{
throw new ExceptionWithConfigData("Latency Simulation enabled, Please enter a end latency !", ConfigData);
}
}
// Check Browser Setting
if (ConfigData.Browser.Value)
{
output("\nValidated Setting -- Browser Simulation enabled");
// Check ACCEPT header Setting
if (string.IsNullOrEmpty(ConfigData.HeaderAccept.Value))
{
throw new ExceptionWithConfigData("Browser Simulation enabled, Please enter an ACCEPT header !", ConfigData);
}
else
{
output("Validated Setting -- ACCEPT (header) => " + ConfigData.HeaderAccept.Value.ToString());
}
// Check ACCEPT-LANG header Setting
if (string.IsNullOrEmpty(ConfigData.HeaderAcceptLang.Value))
{
throw new ExceptionWithConfigData("Browser Simulation enabled, Please enter an ACCEPT-LANG header !", ConfigData);
}
else
{
output("Validated Setting -- ACCEPT-LANG (header) => " + ConfigData.HeaderAcceptLang.Value.ToString());
}
// Check USER-AGENT header Setting
if (string.IsNullOrEmpty(ConfigData.HeaderUserAgent.Value))
{
throw new ExceptionWithConfigData("Browser Simulation enabled, Please enter an USER-AGENT header !", ConfigData);
}
else
{
output("Validated Setting -- USER-AGENT (header) => " + ConfigData.HeaderUserAgent.Value.ToString());
}
}
// Check Dev Cache Settings
if (ConfigData.HardDriveCache.Value == true)
{
output("\nValidated Setting -- DEV Hard Drive Cache enabled");
// Check if Dev Mode enabled !
if (!ConfigData.DevMode.Value)
{
throw new ExceptionWithConfigData("Hard Drive is enabled but not in DEV MODE, Please enable DEV MODE !", ConfigData);
}
// Check Cache Keep Time Setting
if (!string.IsNullOrEmpty(ConfigData.HardDriveCacheKeepTime.Value))
{
try
{
output("Validated Setting -- Cache Keep Time (ms) => " + Convert.ToInt32(ConfigData.HardDriveCacheKeepTime.Value));
}
catch (Exception)
{
throw new ExceptionWithConfigData("Please enter a numeric hard drive keep time in ms !", ConfigData);
}
}
else
{
throw new ExceptionWithConfigData("Hard Drive Cache enabled, Please enter a maximum keep time for cache !", ConfigData);
}
}
else
{
// Delete cache if previously existed
cleanCacheStorage(true);
}
}
}
}

View File

@@ -0,0 +1,273 @@
using CsQuery;
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.Collections.Specialized;
using System.Globalization;
using System.Linq;
using System.Net;
using System.Net.Http;
using System.Text;
using System.Threading.Tasks;
using System.Web;
using Jackett.Models.IndexerConfig;
using System.Text.RegularExpressions;
using System.Xml.Linq;
namespace Jackett.Indexers
{
public class XSpeeds : BaseIndexer, IIndexer
{
string LoginUrl { get { return SiteLink + "takelogin.php"; } }
string GetRSSKeyUrl { get { return SiteLink + "getrss.php"; } }
string SearchUrl { get { return SiteLink + "browse.php"; } }
string RSSUrl { get { return SiteLink + "rss.php?secret_key={0}&feedtype=download&timezone=0&showrows=50&categories=all"; } }
string CommentUrl { get { return SiteLink + "details.php?id={0}"; } }
string DownloadUrl { get { return SiteLink + "download.php?id={0}"; } }
new ConfigurationDataBasicLoginWithRSSAndDisplay configData
{
get {return (ConfigurationDataBasicLoginWithRSSAndDisplay)base.configData; }
set { base.configData = value; }
}
public XSpeeds(IIndexerManagerService i, IWebClient wc, Logger l, IProtectionService ps)
: base(name: "XSpeeds",
description: "XSpeeds",
link: "https://www.xspeeds.eu/",
caps: TorznabUtil.CreateDefaultTorznabTVCaps(),
manager: i,
client: wc,
logger: l,
p: ps,
configData: new ConfigurationDataBasicLoginWithRSSAndDisplay())
{
this.configData.DisplayText.Value = "Expect an initial delay (often around 10 seconds) due to XSpeeds CloudFlare DDoS protection";
this.configData.DisplayText.Name = "Notice";
AddCategoryMapping(70, TorznabCatType.TVAnime);
AddCategoryMapping(80, TorznabCatType.AudioAudiobook);
AddCategoryMapping(66, TorznabCatType.MoviesBluRay);
AddCategoryMapping(48, TorznabCatType.Books);
AddCategoryMapping(68, TorznabCatType.MoviesOther);
AddCategoryMapping(65, TorznabCatType.TVDocumentary);
AddCategoryMapping(10, TorznabCatType.MoviesDVD);
AddCategoryMapping(74, TorznabCatType.TVOTHER);
AddCategoryMapping(44, TorznabCatType.TVSport);
AddCategoryMapping(12, TorznabCatType.Movies);
AddCategoryMapping(13, TorznabCatType.Audio);
AddCategoryMapping(6, TorznabCatType.PC);
AddCategoryMapping(4, TorznabCatType.PC);
AddCategoryMapping(31, TorznabCatType.ConsolePS3);
AddCategoryMapping(31, TorznabCatType.ConsolePS4);
AddCategoryMapping(20, TorznabCatType.TVSport);
AddCategoryMapping(86, TorznabCatType.TVSport);
AddCategoryMapping(47, TorznabCatType.TVHD);
AddCategoryMapping(16, TorznabCatType.TVSD);
AddCategoryMapping(7, TorznabCatType.ConsoleWii);
AddCategoryMapping(8, TorznabCatType.ConsoleXbox);
// RSS Textual categories
AddCategoryMapping("Apps", TorznabCatType.PC);
AddCategoryMapping("Music", TorznabCatType.Audio);
AddCategoryMapping("Audiobooks", TorznabCatType.AudioAudiobook);
}
public async Task<IndexerConfigurationStatus> ApplyConfiguration(JToken configJson)
{
configData.LoadValuesFromJson(configJson);
var pairs = new Dictionary<string, string> {
{ "username", configData.Username.Value },
{ "password", configData.Password.Value }
};
var result = await RequestLoginAndFollowRedirect(LoginUrl, pairs, null, true, null, SiteLink, true);
result = await RequestLoginAndFollowRedirect(LoginUrl, pairs, result.Cookies, true, SearchUrl, SiteLink,true);
await ConfigureIfOK(result.Cookies, result.Content != null && result.Content.Contains("logout.php"), () =>
{
CQ dom = result.Content;
var errorMessage = dom[".left_side table:eq(0) tr:eq(1)"].Text().Trim().Replace("\n\t", " ");
throw new ExceptionWithConfigData(errorMessage, configData);
});
try
{
// Get RSS key
var rssParams = new Dictionary<string, string> {
{ "feedtype", "download" },
{ "timezone", "0" },
{ "showrows", "50" }
};
var rssPage = await PostDataWithCookies(GetRSSKeyUrl, rssParams, result.Cookies);
var match = Regex.Match(rssPage.Content, "(?<=secret_key\\=)([a-zA-z0-9]*)");
configData.RSSKey.Value = match.Success ? match.Value : string.Empty;
if (string.IsNullOrWhiteSpace(configData.RSSKey.Value))
throw new Exception("Failed to get RSS Key");
SaveConfig();
}
catch (Exception e)
{
IsConfigured = false;
throw e;
}
return IndexerConfigurationStatus.RequiresTesting;
}
public async Task<IEnumerable<ReleaseInfo>> PerformQuery(TorznabQuery query)
{
var releases = new List<ReleaseInfo>();
var searchString = query.GetQueryString();
var prevCook = CookieHeader + "";
// If we have no query use the RSS Page as their server is slow enough at times!
if (string.IsNullOrWhiteSpace(searchString))
{
var rssPage = await RequestStringWithCookiesAndRetry(string.Format(RSSUrl, configData.RSSKey.Value));
if (rssPage.Content.EndsWith("\0")) {
rssPage.Content = rssPage.Content.Substring(0, rssPage.Content.Length - 1);
}
var rssDoc = XDocument.Parse(rssPage.Content);
foreach (var item in rssDoc.Descendants("item"))
{
var title = item.Descendants("title").First().Value;
var description = item.Descendants("description").First().Value;
var link = item.Descendants("link").First().Value;
var category = item.Descendants("category").First().Value;
var date = item.Descendants("pubDate").First().Value;
var torrentIdMatch = Regex.Match(link, "(?<=id=)(\\d)*");
var torrentId = torrentIdMatch.Success ? torrentIdMatch.Value : string.Empty;
if (string.IsNullOrWhiteSpace(torrentId))
throw new Exception("Missing torrent id");
var infoMatch = Regex.Match(description, @"Category:\W(?<cat>.*)\W\/\WSeeders:\W(?<seeders>\d*)\W\/\WLeechers:\W(?<leechers>\d*)\W\/\WSize:\W(?<size>[\d\.]*\W\S*)");
if (!infoMatch.Success)
throw new Exception("Unable to find info");
var release = new ReleaseInfo()
{
Title = title,
Description = title,
Guid = new Uri(string.Format(DownloadUrl, torrentId)),
Comments = new Uri(string.Format(CommentUrl, torrentId)),
PublishDate = DateTime.ParseExact(date, "yyyy-MM-dd H:mm:ss", CultureInfo.InvariantCulture), //2015-08-08 21:20:31
Link = new Uri(string.Format(DownloadUrl, torrentId)),
Seeders = ParseUtil.CoerceInt(infoMatch.Groups["seeders"].Value),
Peers = ParseUtil.CoerceInt(infoMatch.Groups["leechers"].Value),
Size = ReleaseInfo.GetBytes(infoMatch.Groups["size"].Value),
Category = MapTrackerCatToNewznab(infoMatch.Groups["cat"].Value)
};
// If its not apps or audio we can only mark as general TV
if (release.Category == 0)
release.Category = 5030;
release.Peers += release.Seeders;
releases.Add(release);
}
}
else
{
if (searchString.Length < 3)
{
OnParseError("", new Exception("Minimum search length is 3"));
return releases;
}
var searchParams = new Dictionary<string, string> {
{ "do", "search" },
{ "keywords", searchString },
{ "search_type", "t_name" },
{ "category", "0" },
{ "include_dead_torrents", "no" }
};
var pairs = new Dictionary<string, string> {
{ "username", configData.Username.Value },
{ "password", configData.Password.Value }
};
var result = await RequestLoginAndFollowRedirect(LoginUrl, pairs, this.CookieHeader, true, null, SiteLink, true);
if (!result.Cookies.Trim().Equals(prevCook.Trim()))
{
result = await RequestLoginAndFollowRedirect(LoginUrl, pairs, result.Cookies, true, SearchUrl, SiteLink, true);
}
this.CookieHeader = result.Cookies;
var attempt = 1;
var searchPage = await PostDataWithCookiesAndRetry(SearchUrl, searchParams,this.CookieHeader);
while (searchPage.IsRedirect && attempt < 3)
{
// add any cookies
var cookieString = this.CookieHeader;
if (searchPage.Cookies != null)
{
cookieString += " " + searchPage.Cookies;
// resolve cookie conflicts - really no need for this as the webclient will handle it
System.Text.RegularExpressions.Regex expression = new System.Text.RegularExpressions.Regex(@"([^\s]+)=([^=]+)(?:\s|$)");
Dictionary<string, string> cookieDIctionary = new Dictionary<string, string>();
var matches = expression.Match(cookieString);
while (matches.Success)
{
if (matches.Groups.Count > 2) cookieDIctionary[matches.Groups[1].Value] = matches.Groups[2].Value;
matches = matches.NextMatch();
}
cookieString = string.Join(" ", cookieDIctionary.Select(kv => kv.Key.ToString() + "=" + kv.Value.ToString()).ToArray());
}
this.CookieHeader = cookieString;
attempt++;
searchPage = await PostDataWithCookiesAndRetry(SearchUrl, searchParams, this.CookieHeader);
}
try
{
CQ dom = searchPage.Content;
var rows = dom["#listtorrents tbody tr"];
foreach (var row in rows.Skip(1))
{
var release = new ReleaseInfo();
var qRow = row.Cq();
release.Title = qRow.Find("td:eq(1) .tooltip-content div:eq(0)").Text();
if (string.IsNullOrWhiteSpace(release.Title))
continue;
release.Description = release.Title;
release.Guid = new Uri(qRow.Find("td:eq(2) a").Attr("href"));
release.Link = release.Guid;
release.Comments = new Uri(qRow.Find("td:eq(1) .tooltip-target a").Attr("href"));
release.PublishDate = DateTime.ParseExact(qRow.Find("td:eq(1) div").Last().Text().Trim(), "dd-MM-yyyy H:mm", CultureInfo.InvariantCulture, DateTimeStyles.AssumeUniversal); //08-08-2015 12:51
release.Seeders = ParseUtil.CoerceInt(qRow.Find("td:eq(6)").Text());
release.Peers = release.Seeders + ParseUtil.CoerceInt(qRow.Find("td:eq(7)").Text().Trim());
release.Size = ReleaseInfo.GetBytes(qRow.Find("td:eq(4)").Text().Trim());
var cat = row.Cq().Find("td:eq(0) a").First().Attr("href");
var catSplit = cat.LastIndexOf('=');
if (catSplit > -1)
cat = cat.Substring(catSplit + 1);
release.Category = MapTrackerCatToNewznab(cat);
// If its not apps or audio we can only mark as general TV
if (release.Category == 0)
release.Category = 5030;
releases.Add(release);
}
}
catch (Exception ex)
{
OnParseError(searchPage.Content, ex);
}
}
if (!CookieHeader.Trim().Equals(prevCook.Trim()))
{
this.SaveConfig();
}
return releases;
}
}
}

View File

@@ -0,0 +1,592 @@
using System;
using System.Collections.Generic;
using System.Collections.Specialized;
using System.Linq;
using System.Reflection;
using System.Threading.Tasks;
using System.Web;
using Jackett.Models;
using Jackett.Models.IndexerConfig.Bespoke;
using Jackett.Services;
using Jackett.Utils;
using Jackett.Utils.Clients;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using NLog;
namespace Jackett.Indexers
{
/// <summary>
/// Provider for Xthor Private French Tracker
/// </summary>
public class Xthor : BaseIndexer, IIndexer
{
private static string ApiEndpoint => "https://api.xthor.bz/";
private string TorrentCommentUrl => TorrentDescriptionUrl;
private string TorrentDescriptionUrl => SiteLink + "details.php?id={id}";
private bool DevMode => ConfigData.DevMode.Value;
private bool CacheMode => ConfigData.HardDriveCache.Value;
private static string Directory => System.IO.Path.GetTempPath() + "Jackett\\" + MethodBase.GetCurrentMethod().DeclaringType?.Name + "\\";
public Dictionary<string, string> EmulatedBrowserHeaders { get; } = new Dictionary<string, string>();
private ConfigurationDataXthor ConfigData => (ConfigurationDataXthor)configData;
public Xthor(IIndexerManagerService i, IWebClient w, Logger l, IProtectionService ps)
: base(
name: "Xthor",
description: "General French Private Tracker",
link: "https://xthor.bz/",
caps: new TorznabCapabilities(),
manager: i,
client: w,
logger: l,
p: ps,
downloadBase: "https://xthor.bz/download.php?torrent=",
configData: new ConfigurationDataXthor())
{
// Clean capabilities
TorznabCaps.Categories.Clear();
// Movies
AddCategoryMapping(6, TorznabCatType.MoviesSD); // XVID
AddCategoryMapping(7, TorznabCatType.MoviesSD); // X264
AddCategoryMapping(95, TorznabCatType.MoviesSD); // WEBRIP
AddCategoryMapping(5, TorznabCatType.MoviesHD); // HD 720P
AddCategoryMapping(4, TorznabCatType.MoviesHD); // HD 1080P X264
AddCategoryMapping(100, TorznabCatType.MoviesHD); // HD 1080P X265
AddCategoryMapping(94, TorznabCatType.MoviesHD); // WEBDL
AddCategoryMapping(1, TorznabCatType.MoviesBluRay); // FULL BLURAY
AddCategoryMapping(2, TorznabCatType.MoviesBluRay); // BLURAY REMUX
AddCategoryMapping(3, TorznabCatType.MoviesBluRay); // FULL BLURAY 3D
AddCategoryMapping(8, TorznabCatType.MoviesDVD); // FULL DVD
AddCategoryMapping(9, TorznabCatType.MoviesOther); // VOSTFR
AddCategoryMapping(36, TorznabCatType.XXX); // XXX
// Series
AddCategoryMapping(14, TorznabCatType.TVSD); // SD VF
AddCategoryMapping(16, TorznabCatType.TVSD); // SD VF VOSTFR
AddCategoryMapping(15, TorznabCatType.TVHD); // HD VF
AddCategoryMapping(17, TorznabCatType.TVHD); // HD VF VOSTFR
AddCategoryMapping(13, TorznabCatType.TVOTHER); // PACK
AddCategoryMapping(98, TorznabCatType.TVOTHER); // PACK VOSTFR HD
AddCategoryMapping(16, TorznabCatType.TVOTHER); // PACK VOSTFR SD
AddCategoryMapping(30, TorznabCatType.TVOTHER); // EMISSIONS
AddCategoryMapping(34, TorznabCatType.TVOTHER); // EMISSIONS
AddCategoryMapping(33, TorznabCatType.TVOTHER); // SHOWS
// Anime
AddCategoryMapping(31, TorznabCatType.TVAnime); // MOVIES ANIME
AddCategoryMapping(32, TorznabCatType.TVAnime); // SERIES ANIME
// Documentaries
AddCategoryMapping(12, TorznabCatType.TVDocumentary); // DOCS
// Music
AddCategoryMapping(20, TorznabCatType.AudioVideo); // CONCERT
// Other
AddCategoryMapping(21, TorznabCatType.PC); // PC
AddCategoryMapping(22, TorznabCatType.PCMac); // PC
AddCategoryMapping(25, TorznabCatType.PCGames); // GAMES
AddCategoryMapping(26, TorznabCatType.ConsoleXbox360); // GAMES
AddCategoryMapping(28, TorznabCatType.ConsoleWii); // GAMES
AddCategoryMapping(27, TorznabCatType.ConsolePS3); // GAMES
AddCategoryMapping(29, TorznabCatType.ConsoleNDS); // GAMES
AddCategoryMapping(24, TorznabCatType.BooksEbook); // EBOOKS
AddCategoryMapping(96, TorznabCatType.BooksEbook); // EBOOKS MAGAZINES
AddCategoryMapping(99, TorznabCatType.BooksEbook); // EBOOKS ANIME
AddCategoryMapping(23, TorznabCatType.PCPhoneAndroid); // ANDROID
}
/// <summary>
/// Configure our Provider
/// </summary>
/// <param name="configJson">Our params in Json</param>
/// <returns>Configuration state</returns>
#pragma warning disable 1998
public async Task<IndexerConfigurationStatus> ApplyConfiguration(JToken configJson)
#pragma warning restore 1998
{
// Provider not yet configured
IsConfigured = false;
// Retrieve config values set by Jackett's user
ConfigData.LoadValuesFromJson(configJson);
// Check & Validate Config
ValidateConfig();
// Setting our data for a better emulated browser (maximum security)
// TODO: Encoded Content not supported by Jackett at this time
// emulatedBrowserHeaders.Add("Accept-Encoding", "gzip, deflate");
// Clean headers
EmulatedBrowserHeaders.Clear();
// Inject headers
EmulatedBrowserHeaders.Add("Accept", "application/json-rpc, application/json");
EmulatedBrowserHeaders.Add("Content-Type", "application/json-rpc");
// Tracker is now configured
IsConfigured = true;
// Saving data
SaveConfig();
return IndexerConfigurationStatus.RequiresTesting;
}
/// <summary>
/// Execute our search query
/// </summary>
/// <param name="query">Query</param>
/// <returns>Releases</returns>
public async Task<IEnumerable<ReleaseInfo>> PerformQuery(TorznabQuery query)
{
var releases = new List<ReleaseInfo>();
var searchTerm = query.GetQueryString();
// Check cache first so we don't query the server (if search term used or not in dev mode)
if(!DevMode && !string.IsNullOrEmpty(searchTerm))
{
lock (cache)
{
// Remove old cache items
CleanCache();
// Search in cache
var cachedResult = cache.FirstOrDefault(i => i.Query == searchTerm);
if (cachedResult != null)
return cachedResult.Results.Select(s => (ReleaseInfo)s.Clone()).ToArray();
}
}
// Build our query
var request = BuildQuery(searchTerm, query, ApiEndpoint);
// Getting results & Store content
var results = await QueryExec(request);
try
{
// Deserialize our Json Response
var xthorResponse = JsonConvert.DeserializeObject<XthorResponse>(results.Content);
// Check Tracker's State
CheckApiState(xthorResponse.error);
// If contains torrents
if (xthorResponse.torrents != null)
{
// Adding each torrent row to releases
releases.AddRange(xthorResponse.torrents.Select(torrent => new ReleaseInfo
{
// Mapping data
Category = MapTrackerCatToNewznab(torrent.category.ToString()),
Title = torrent.name, Seeders = torrent.seeders,
Peers = torrent.seeders + torrent.leechers,
MinimumRatio = 1,
MinimumSeedTime = 345600,
PublishDate = DateTimeUtil.UnixTimestampToDateTime(torrent.added),
Size = torrent.size,
Guid = new Uri(TorrentDescriptionUrl.Replace("{id}", torrent.id.ToString())),
Comments = new Uri(TorrentCommentUrl.Replace("{id}", torrent.id.ToString())),
Link = new Uri(torrent.download_link)
}));
}
}
catch (Exception ex)
{
OnParseError("Error, unable to parse result \n" + ex.StackTrace, ex);
}
// Return found releases
return releases;
}
/// <summary>
/// Response from Tracker's API
/// </summary>
public class XthorResponse
{
public XthorError error { get; set; }
public XthorUser user { get; set; }
public List<XthorTorrent> torrents { get; set; }
}
/// <summary>
/// State of API
/// </summary>
public class XthorError
{
public int code { get; set; }
public string descr { get; set; }
}
/// <summary>
/// User Informations
/// </summary>
public class XthorUser
{
public int id { get; set; }
public string username { get; set; }
public long uploaded { get; set; }
public long downloaded { get; set; }
public int uclass { get; set; } // Class is a reserved keyword.
public decimal bonus_point { get; set; }
public int hits_and_run { get; set; }
public string avatar_url { get; set; }
}
/// <summary>
/// Torrent Informations
/// </summary>
public class XthorTorrent
{
public int id { get; set; }
public int category { get; set; }
public int seeders { get; set; }
public int leechers { get; set; }
public string name { get; set; }
public int times_completed { get; set; }
public long size { get; set; }
public int added { get; set; }
public int freeleech { get; set; }
public int numfiles { get; set; }
public string release_group { get; set; }
public string download_link { get; set; }
}
/// <summary>
/// Build query to process
/// </summary>
/// <param name="term">Term to search</param>
/// <param name="query">Torznab Query for categories mapping</param>
/// <param name="url">Search url for provider</param>
/// <returns>URL to query for parsing and processing results</returns>
private string BuildQuery(string term, TorznabQuery query, string url)
{
var parameters = new NameValueCollection();
var categoriesList = MapTorznabCapsToTrackers(query);
// Passkey
parameters.Add("passkey", ConfigData.PassKey.Value);
// If search term provided
if (!string.IsNullOrWhiteSpace(term))
{
// Add search term
// ReSharper disable once AssignNullToNotNullAttribute
parameters.Add("search", HttpUtility.UrlEncode(term));
}
else
{
parameters.Add("search", string.Empty);
// Showing all torrents (just for output function)
term = "all";
}
// Loop on Categories needed
switch (categoriesList.Count)
{
case 0:
// No category
parameters.Add("category", string.Empty);
break;
case 1:
// One category
parameters.Add("category", categoriesList[0]);
break;
default:
// Multiple Categories
string categories = null;
foreach (var category in categoriesList)
{
// Initiate our categories parameter
if (categoriesList.First() == category)
{
categories = categoriesList[0];
}
// Adding next categories
categories += "+" + category;
}
// Add categories
if (categories != null) parameters.Add("category", categories);
break;
}
// If Only Freeleech Enabled
if (ConfigData.Freeleech.Value)
{
parameters.Add("freeleech", "1");
}
// Building our query -- Cannot use GetQueryString due to UrlEncode (generating wrong category param)
url += "?" + string.Join("&", parameters.AllKeys.Select(a => a + "=" + parameters[a]));
Output("\nBuilded query for \"" + term + "\"... " + url);
// Return our search url
return url;
}
/// <summary>
/// Switch Method for Querying
/// </summary>
/// <param name="request">URL created by Query Builder</param>
/// <returns>Results from query</returns>
private async Task<WebClientStringResult> QueryExec(string request)
{
WebClientStringResult results;
// Switch in we are in DEV mode with Hard Drive Cache or not
if (DevMode && CacheMode)
{
// Check Cache before querying and load previous results if available
results = await QueryCache(request);
}
else
{
// Querying tracker directly
results = await QueryTracker(request);
}
return results;
}
/// <summary>
/// Get Torrents Page from Cache by Query Provided
/// </summary>
/// <param name="request">URL created by Query Builder</param>
/// <returns>Results from query</returns>
private async Task<WebClientStringResult> QueryCache(string request)
{
WebClientStringResult results;
// Create Directory if not exist
System.IO.Directory.CreateDirectory(Directory);
// Clean Storage Provider Directory from outdated cached queries
CleanCacheStorage();
// Create fingerprint for request
string file = Directory + request.GetHashCode() + ".json";
// Checking modes states
if (System.IO.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));
}
else
{
// No cached file found, querying tracker directly
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));
}
return results;
}
/// <summary>
/// Get Torrents Page from Tracker by Query Provided
/// </summary>
/// <param name="request">URL created by Query Builder</param>
/// <returns>Results from query</returns>
private async Task<WebClientStringResult> QueryTracker(string request)
{
// Cache mode not enabled or cached file didn't exist for our query
Output("\nQuerying tracker for results....");
// Build WebRequest for index
var myIndexRequest = new WebRequest()
{
Type = RequestType.GET,
Url = request,
Headers = EmulatedBrowserHeaders
};
// Request our first page
var results = await webclient.GetString(myIndexRequest);
// Return results from tracker
return results;
}
/// <summary>
/// Check API's state
/// </summary>
/// <param name="state">State of API</param>
private void CheckApiState(XthorError state)
{
// Switch on state
switch (state.code)
{
case 0:
// Everything OK
Output("\nAPI State : Everything OK ... -> " + state.descr);
break;
case 1:
// Passkey not found
Output("\nAPI State : Error, Passkey not found in tracker's database, aborting... -> " + state.descr);
throw new Exception("API State : Error, Passkey not found in tracker's database, aborting... -> " + state.descr);
case 2:
// No results
Output("\nAPI State : No results for query ... -> " + state.descr);
break;
case 3:
// Power Saver
Output("\nAPI State : Power Saver mode, only cached query with no parameters available ... -> " + state.descr);
break;
case 4:
// DDOS Attack, API disabled
Output("\nAPI State : Tracker is under DDOS attack, API disabled, aborting ... -> " + state.descr);
throw new Exception("\nAPI State : Tracker is under DDOS attack, API disabled, aborting ... -> " + state.descr);
default:
// Unknown state
Output("\nAPI State : Unknown state, aborting querying ... -> " + state.descr);
throw new Exception("API State : Unknown state, aborting querying ... -> " + state.descr);
}
}
/// <summary>
/// Clean Hard Drive Cache Storage
/// </summary>
/// <param name="force">Force Provider Folder deletion</param>
private void CleanCacheStorage(bool force = false)
{
// Check cleaning method
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))
{
// Delete storage directory of provider
System.IO.Directory.Delete(Directory, true);
Output("-> Storage folder deleted successfully.");
}
else
{
// No directory, so nothing to do
Output("-> No Storage folder found for this provider !");
}
}
else
{
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))
.Where(f => f.LastAccessTime < DateTime.Now.AddMilliseconds(-Convert.ToInt32(ConfigData.HardDriveCacheKeepTime.Value)))
.ToList()
.ForEach(f => {
Output("Deleting cached file << " + f.Name + " >> ... done.");
f.Delete();
i++;
});
// Inform on what was cleaned during process
if(i > 0) {
Output("-> Deleted " + i + " cached files during cleaning.");
}
else {
Output("-> Nothing deleted during cleaning.");
}
}
}
/// <summary>
/// Output message for logging or developpment (console)
/// </summary>
/// <param name="message">Message to output</param>
/// <param name="level">Level for Logger</param>
private void Output(string message, string level = "debug")
{
// Check if we are in dev mode
if(DevMode)
{
// Output message to console
Console.WriteLine(message);
}
else
{
// Send message to logger with level
switch (level)
{
default:
goto case "debug";
case "debug":
// Only if Debug Level Enabled on Jackett
if (Engine.Logger.IsDebugEnabled)
{
logger.Debug(message);
}
break;
case "info":
logger.Info(message);
break;
case "error":
logger.Error(message);
break;
}
}
}
/// <summary>
/// Validate Config entered by user on Jackett
/// </summary>
private void ValidateConfig()
{
Output("\nValidating Settings ... \n");
// Check Passkey Setting
if (string.IsNullOrEmpty(ConfigData.PassKey.Value))
{
throw new ExceptionWithConfigData("You must provide your passkey for this tracker to be allowed to use API !", ConfigData);
}
else
{
Output("Validated Setting -- PassKey (auth) => " + ConfigData.PassKey.Value);
}
// Check Dev Cache Settings
if (ConfigData.HardDriveCache.Value)
{
Output("\nValidated Setting -- DEV Hard Drive Cache enabled");
// Check if Dev Mode enabled !
if (!ConfigData.DevMode.Value)
{
throw new ExceptionWithConfigData("Hard Drive is enabled but not in DEV MODE, Please enable DEV MODE !", ConfigData);
}
// Check Cache Keep Time Setting
if (!string.IsNullOrEmpty(ConfigData.HardDriveCacheKeepTime.Value))
{
try
{
Output("Validated Setting -- Cache Keep Time (ms) => " + Convert.ToInt32(ConfigData.HardDriveCacheKeepTime.Value));
}
catch (Exception)
{
throw new ExceptionWithConfigData("Please enter a numeric hard drive keep time in ms !", ConfigData);
}
}
else
{
throw new ExceptionWithConfigData("Hard Drive Cache enabled, Please enter a maximum keep time for cache !", ConfigData);
}
}
else
{
// Delete cache if previously existed
CleanCacheStorage(true);
}
}
}
}

View File

@@ -58,6 +58,10 @@
</StartupObject>
</PropertyGroup>
<ItemGroup>
<Reference Include="AngleSharp, Version=0.9.5.41771, Culture=neutral, PublicKeyToken=e83494dcdc6d31ea, processorArchitecture=MSIL">
<HintPath>..\packages\AngleSharp.0.9.5\lib\net45\AngleSharp.dll</HintPath>
<Private>True</Private>
</Reference>
<Reference Include="Autofac, Version=3.5.0.0, Culture=neutral, PublicKeyToken=17863af14b0044da, processorArchitecture=MSIL">
<HintPath>..\packages\Autofac.3.5.2\lib\net40\Autofac.dll</HintPath>
<Private>True</Private>
@@ -182,7 +186,12 @@
<Compile Include="Controllers\TorznabController.cs" />
<Compile Include="Controllers\DownloadController.cs" />
<Compile Include="Engine.cs" />
<Compile Include="Indexers\Hebits.cs" />
<Compile Include="Indexers\MyAnonamouse.cs" />
<Compile Include="Indexers\PassThePopcorn.cs" />
<Compile Include="Indexers\Xthor.cs" />
<Compile Include="Indexers\AlphaRatio.cs" />
<Compile Include="Indexers\BitSoup.cs" />
<Compile Include="Indexers\BlueTigers.cs" />
<Compile Include="Indexers\EuTorrents.cs" />
<Compile Include="Indexers\Avistaz.cs" />
@@ -194,7 +203,11 @@
<Compile Include="Indexers\Demonoid.cs" />
<Compile Include="Indexers\BroadcastTheNet.cs" />
<Compile Include="Indexers\DanishBits.cs" />
<Compile Include="Indexers\Abnormal.cs" />
<Compile Include="Indexers\Fuzer.cs" />
<Compile Include="Indexers\GFTracker.cs" />
<Compile Include="Indexers\ILoveTorrents.cs" />
<Compile Include="Indexers\RevolutionTT.cs" />
<Compile Include="Indexers\TehConnection.cs" />
<Compile Include="Indexers\Hounddawgs.cs" />
<Compile Include="Indexers\Shazbat.cs" />
@@ -205,11 +218,23 @@
<Compile Include="Indexers\ImmortalSeed.cs" />
<Compile Include="Indexers\FileList.cs" />
<Compile Include="Indexers\Abstract\AvistazTracker.cs" />
<Compile Include="Indexers\FrenchADN.cs" />
<Compile Include="Indexers\TransmitheNet.cs" />
<Compile Include="Indexers\WiHD.cs" />
<Compile Include="Indexers\XSpeeds.cs" />
<Compile Include="Models\GitHub\Asset.cs" />
<Compile Include="Models\GitHub\Release.cs" />
<Compile Include="Models\IndexerConfig\Bespoke\ConfigurationDataXthor.cs" />
<Compile Include="Models\IndexerConfig\Bespoke\ConfigurationDataPhxBit.cs" />
<Compile Include="Models\IndexerConfig\Bespoke\ConfigurationDataBlueTigers.cs" />
<Compile Include="Models\IndexerConfig\Bespoke\ConfigurationDataAbnormal.cs" />
<Compile Include="Models\IndexerConfig\Bespoke\ConfigurationDataFrenchADN.cs" />
<Compile Include="Models\IndexerConfig\Bespoke\ConfigurationDataWiHD.cs" />
<Compile Include="Models\IndexerConfig\ConfigurationDataBasicLoginWithFilterAndPasskey.cs" />
<Compile Include="Models\IndexerConfig\ConfigurationDataBasicLoginWithFilter.cs" />
<Compile Include="Models\IndexerConfig\ConfigurationDataAPIKey.cs" />
<Compile Include="Models\IndexerConfig\ConfigurationDataBasicLoginWithRSSAndDisplay.cs" />
<Compile Include="Models\IndexerConfig\ConfigurationDataBasicLoginWithAlternateLink.cs" />
<Compile Include="Models\ManualSearchResult.cs" />
<Compile Include="Indexers\TVChaosUK.cs" />
<Compile Include="Indexers\NCore.cs" />
@@ -219,6 +244,7 @@
<Compile Include="Indexers\Pretome.cs" />
<Compile Include="Indexers\PrivateHD.cs" />
<Compile Include="Indexers\SceneAccess.cs" />
<Compile Include="Indexers\SceneFZ.cs" />
<Compile Include="Indexers\SceneTime.cs" />
<Compile Include="Indexers\SpeedCD.cs" />
<Compile Include="Indexers\TorrentDay.cs" />
@@ -304,6 +330,7 @@
<Compile Include="Startup.cs" />
<Compile Include="Models\TorznabQuery.cs" />
<Compile Include="CurlHelper.cs" />
<Compile Include="Utils\StringCipher.cs" />
<Compile Include="Utils\StringUtil.cs" />
<Compile Include="Utils\TorznabCapsUtil.cs" />
<Compile Include="Utils\Clients\UnixSafeCurlWebClient.cs" />
@@ -422,6 +449,12 @@
<Content Include="Content\login.html">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
<Content Include="Content\logos\myanonamouse.png">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
<Content Include="Content\logos\xthor.png">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
<Content Include="Content\logos\animebytes.png">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
@@ -434,6 +467,9 @@
<Content Include="Content\logos\beyondhd.png">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
<Content Include="Content\logos\bitsoup.png">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
<Content Include="Content\logos\bluetigers.png">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
@@ -452,6 +488,9 @@
<Content Include="Content\logos\filelist.png">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
<Content Include="Content\logos\fuzer.png">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
<Content Include="Content\logos\gftracker.png">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
@@ -461,6 +500,9 @@
<Content Include="Content\logos\hdtorrents.png">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
<Content Include="Content\logos\ilovetorrents.png">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
<Content Include="Content\logos\immortalseed.png">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
@@ -476,9 +518,15 @@
<Content Include="Content\logos\privatehd.png">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
<Content Include="Content\logos\revolutiontt.png">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
<Content Include="Content\logos\sceneaccess.png">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
<Content Include="Content\logos\scenefz.png">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
<Content Include="Content\logos\scenetime.png">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
@@ -551,12 +599,30 @@
<Content Include="Content\logos\torrentleech.png">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
<Content Include="Content\logos\transmithenet.png">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
<Content Include="Content\logos\tvchaosuk.png">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
<Content Include="Content\logos\frenchadn.png">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
<Content Include="Content\logos\abnormal.png">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
<Content Include="Content\logos\wihd.png">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
<Content Include="Content\logos\xspeeds.png">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
<Content Include="Content\setup_indexer.html">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
<Content Include="Content\logos\hebits.png">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
<Content Include="Models\TorznabCatType.tt">
<Generator>TextTemplatingFileGenerator</Generator>
<LastGenOutput>TorznabCatType.generated.cs</LastGenOutput>

View File

@@ -23,6 +23,7 @@ namespace Jackett.Models.Config
public string BlackholeDir { get; set; }
public bool UpdateDisabled { get; set; }
public bool UpdatePrerelease { get; set; }
public string BasePathOverride { get; set; }
public string[] GetListenAddresses(bool? external = null)
{

View File

@@ -0,0 +1,57 @@
namespace Jackett.Models.IndexerConfig.Bespoke
{
class ConfigurationDataAbnormal : ConfigurationData
{
public HiddenItem AuthKey { get; set; }
public HiddenItem TorrentPass { get; set; }
public DisplayItem CredentialsWarning { get; private set; }
public StringItem Username { get; private set; }
public StringItem Password { get; private set; }
public DisplayItem PagesWarning { get; private set; }
public StringItem Pages { get; private set; }
public DisplayItem SecurityWarning { get; private set; }
public BoolItem Latency { get; private set; }
public BoolItem Browser { get; private set; }
public DisplayItem LatencyWarning { get; private set; }
public StringItem LatencyStart { get; private set; }
public StringItem LatencyEnd { get; private set; }
public DisplayItem HeadersWarning { get; private set; }
public StringItem HeaderAccept { get; private set; }
public StringItem HeaderAcceptLang { get; private set; }
public BoolItem HeaderDNT { get; private set; }
public BoolItem HeaderUpgradeInsecure { get; private set; }
public StringItem HeaderUserAgent { get; private set; }
public DisplayItem DevWarning { get; private set; }
public BoolItem DevMode { get; private set; }
public BoolItem HardDriveCache { get; private set; }
public StringItem HardDriveCacheKeepTime { get; private set; }
public ConfigurationDataAbnormal()
: base()
{
AuthKey = new HiddenItem { Name = "AuthKey", Value = "" };
TorrentPass = new HiddenItem { Name = "TorrentPass", Value = "" };
CredentialsWarning = new DisplayItem("<b>Credentials Configuration</b> (<i>Private Tracker</i>),<br /><br /> <ul><li><b>Username</b> is your account name on this tracker.</li><li><b>Password</b> is your password associated to your account name.</li></ul>") { Name = "Credentials" };
Username = new StringItem { Name = "Username (Required)", Value = "" };
Password = new StringItem { Name = "Password (Required)", Value = "" };
PagesWarning = new DisplayItem("<b>Preferences Configuration</b> (<i>Tweak your search settings</i>),<br /><br /> <ul><li><b>Max Pages to Process</b> let you specify how many page (max) Jackett can process when doing a search. Setting a value <b>higher than 4 is dangerous</b> for you account ! (<b>Result of too many requests to tracker...that <u>will be suspect</u></b>).</li><li><b>With Dead Torrents</b> let you search <u>with</u> torrents which are marked Dead.</li><li><b>Freeleech Only</b> let you search <u>only</u> for torrents which are marked Freeleech.</li><li><b>With Nuked Releases</b> let you search <u>with</u> torrents which are marked Nuked.</li><li><b>3D Releases Only</b> let you search <u>only</u> for torrents which are marked 3D.</li><li><b>French Only</b> let you search <u>only</u> for torrents which are marked FRENCH or TRUEFRENCH (<i>MULTI not included !</i>).</li></ul>") { Name = "Preferences" };
Pages = new StringItem { Name = "Max Pages to Process (Required)", Value = "4" };
SecurityWarning = new DisplayItem("<b>Security Configuration</b> (<i>Read this area carefully !</i>),<br /><br /> <ul><li><b>Latency Simulation</b> will simulate human browsing with Jacket by pausing Jacket for an random time between each request, to fake a real content browsing.</li><li><b>Browser Simulation</b> will simulate a real human browser by injecting additionals headers when doing requests to tracker.</li></ul>") { Name = "Security" };
Latency = new BoolItem() { Name = "Latency Simulation (Optional)", Value = false };
Browser = new BoolItem() { Name = "Browser Simulation (Optional)", Value = true };
LatencyWarning = new DisplayItem("<b>Latency Configuration</b> (<i>Required if latency simulation enabled</i>),<br /><br/> <ul><li>By filling this range, <b>Jackett will make a random timed pause</b> <u>between requests</u> to tracker <u>to simulate a real browser</u>.</li><li>MilliSeconds <b>only</b></li></ul>") { Name = "Simulate Latency" };
LatencyStart = new StringItem { Name = "Minimum Latency (ms)", Value = "1589" };
LatencyEnd = new StringItem { Name = "Maximum Latency (ms)", Value = "3674" };
HeadersWarning = new DisplayItem("<b>Browser Headers Configuration</b> (<i>Required if browser simulation enabled</i>),<br /><br /> <ul><li>By filling these fields, <b>Jackett will inject headers</b> with your values <u>to simulate a real browser</u>.</li><li>You can get <b>your browser values</b> here: <a href='https://www.whatismybrowser.com/detect/what-http-headers-is-my-browser-sending' target='blank'>www.whatismybrowser.com</a></li></ul><br /><i><b>Note that</b> some headers are not necessary because they are injected automatically by this provider such as Accept_Encoding, Connection, Host or X-Requested-With</i>") { Name = "Injecting headers" };
HeaderAccept = new StringItem { Name = "Accept", Value = "" };
HeaderAcceptLang = new StringItem { Name = "Accept-Language", Value = "" };
HeaderDNT = new BoolItem { Name = "DNT", Value = false };
HeaderUpgradeInsecure = new BoolItem { Name = "Upgrade-Insecure-Requests", Value = false };
HeaderUserAgent = new StringItem { Name = "User-Agent", Value = "" };
DevWarning = new DisplayItem("<b>Development Facility</b> (<i>For Developers ONLY</i>),<br /><br /> <ul><li>By enabling development mode, <b>Jackett will bypass his cache</b> and will <u>output debug messages to console</u> instead of his log file.</li><li>By enabling Hard Drive Cache, <b>This provider</b> will <u>save each query answers from tracker</u> in temp directory, in fact this reduce drastically HTTP requests when building a provider at parsing step for example. So, <b> Jackett will search for a cached query answer on hard drive before executing query on tracker side !</b> <i>DEV MODE must be enabled to use it !</li></ul>") { Name = "Development" };
DevMode = new BoolItem { Name = "Enable DEV MODE (Developers ONLY)", Value = false };
HardDriveCache = new BoolItem { Name = "Enable HARD DRIVE CACHE (Developers ONLY)", Value = false };
HardDriveCacheKeepTime = new StringItem { Name = "Keep Cached files for (ms)", Value = "300000" };
}
}
}

View File

@@ -11,12 +11,14 @@ namespace Jackett.Models.IndexerConfig.Bespoke
{
public BoolItem IncludeRaw { get; private set; }
public DisplayItem DateWarning { get; private set; }
public BoolItem InsertSeason { get; private set; }
public ConfigurationDataAnimeBytes()
: base()
{
IncludeRaw = new BoolItem() { Name = "IncludeRaw", Value = false };
DateWarning = new DisplayItem("This tracker does not supply upload dates so they are based off year of release.") { Name = "DateWarning" };
InsertSeason = new BoolItem() { Name = "Prefix episode number with S01 for Sonarr Compatability", Value = false };
}
}
}

View File

@@ -0,0 +1,52 @@
namespace Jackett.Models.IndexerConfig.Bespoke
{
internal class ConfigurationDataFrenchAdn : ConfigurationData
{
public DisplayItem CredentialsWarning { get; private set; }
public StringItem Username { get; private set; }
public StringItem Password { get; private set; }
public DisplayItem PagesWarning { get; private set; }
public StringItem Pages { get; private set; }
public DisplayItem SecurityWarning { get; private set; }
public BoolItem Latency { get; private set; }
public BoolItem Browser { get; private set; }
public DisplayItem LatencyWarning { get; private set; }
public StringItem LatencyStart { get; private set; }
public StringItem LatencyEnd { get; private set; }
public DisplayItem HeadersWarning { get; private set; }
public StringItem HeaderAccept { get; private set; }
public StringItem HeaderAcceptLang { get; private set; }
public BoolItem HeaderDnt { get; private set; }
public BoolItem HeaderUpgradeInsecure { get; private set; }
public StringItem HeaderUserAgent { get; private set; }
public DisplayItem DevWarning { get; private set; }
public BoolItem DevMode { get; private set; }
public BoolItem HardDriveCache { get; private set; }
public StringItem HardDriveCacheKeepTime { get; private set; }
public ConfigurationDataFrenchAdn()
{
CredentialsWarning = new DisplayItem("<b>Credentials Configuration</b> (<i>Private Tracker</i>),<br /><br /> <ul><li><b>Username</b> is your account name on this tracker.</li><li><b>Password</b> is your password associated to your account name.</li></ul>") { Name = "Credentials" };
Username = new StringItem { Name = "Username (Required)", Value = "" };
Password = new StringItem { Name = "Password (Required)", Value = "" };
PagesWarning = new DisplayItem("<b>Preferences Configuration</b> (<i>Tweak your search settings</i>),<br /><br /> <ul><li><b>Max Pages to Process</b> let you specify how many page (max) Jackett can process when doing a search. Setting a value <b>higher than 4 is dangerous</b> for you account ! (<b>Result of too many requests to tracker...that <u>will be suspect</u></b>).</li></ul>") { Name = "Preferences" };
Pages = new StringItem { Name = "Max Pages to Process (Required)", Value = "4" };
SecurityWarning = new DisplayItem("<b>Security Configuration</b> (<i>Read this area carefully !</i>),<br /><br /> <ul><li><b>Latency Simulation</b> will simulate human browsing with Jacket by pausing Jacket for an random time between each request, to fake a real content browsing.</li><li><b>Browser Simulation</b> will simulate a real human browser by injecting additionals headers when doing requests to tracker.<b>You must enable it to use this provider!</b></li></ul>") { Name = "Security" };
Latency = new BoolItem() { Name = "Latency Simulation (Optional)", Value = false };
Browser = new BoolItem() { Name = "Browser Simulation (Forced)", Value = true };
LatencyWarning = new DisplayItem("<b>Latency Configuration</b> (<i>Required if latency simulation enabled</i>),<br /><br/> <ul><li>By filling this range, <b>Jackett will make a random timed pause</b> <u>between requests</u> to tracker <u>to simulate a real browser</u>.</li><li>MilliSeconds <b>only</b></li></ul>") { Name = "Simulate Latency" };
LatencyStart = new StringItem { Name = "Minimum Latency (ms)", Value = "1589" };
LatencyEnd = new StringItem { Name = "Maximum Latency (ms)", Value = "3674" };
HeadersWarning = new DisplayItem("<b>Browser Headers Configuration</b> (<i>Required if browser simulation enabled</i>),<br /><br /> <ul><li>By filling these fields, <b>Jackett will inject headers</b> with your values <u>to simulate a real browser</u>.</li><li>You can get <b>your browser values</b> here: <a href='https://www.whatismybrowser.com/detect/what-http-headers-is-my-browser-sending' target='blank'>www.whatismybrowser.com</a></li></ul><br /><i><b>Note that</b> some headers are not necessary because they are injected automatically by this provider such as Accept_Encoding, Connection, Host or X-Requested-With</i>") { Name = "Injecting headers" };
HeaderAccept = new StringItem { Name = "Accept", Value = "" };
HeaderAcceptLang = new StringItem { Name = "Accept-Language", Value = "" };
HeaderDnt = new BoolItem { Name = "DNT", Value = false };
HeaderUpgradeInsecure = new BoolItem { Name = "Upgrade-Insecure-Requests", Value = false };
HeaderUserAgent = new StringItem { Name = "User-Agent", Value = "" };
DevWarning = new DisplayItem("<b>Development Facility</b> (<i>For Developers ONLY</i>),<br /><br /> <ul><li>By enabling development mode, <b>Jackett will bypass his cache</b> and will <u>output debug messages to console</u> instead of his log file.</li><li>By enabling Hard Drive Cache, <b>This provider</b> will <u>save each query answers from tracker</u> in temp directory, in fact this reduce drastically HTTP requests when building a provider at parsing step for example. So, <b> Jackett will search for a cached query answer on hard drive before executing query on tracker side !</b> <i>DEV MODE must be enabled to use it !</li></ul>") { Name = "Development" };
DevMode = new BoolItem { Name = "Enable DEV MODE (Developers ONLY)", Value = false };
HardDriveCache = new BoolItem { Name = "Enable HARD DRIVE CACHE (Developers ONLY)", Value = false };
HardDriveCacheKeepTime = new StringItem { Name = "Keep Cached files for (ms)", Value = "300000" };
}
}
}

View File

@@ -0,0 +1,55 @@
namespace Jackett.Models.IndexerConfig.Bespoke
{
class ConfigurationDataPhxBit : ConfigurationData
{
public HiddenItem PassKey { get; set; }
public DisplayItem CredentialsWarning { get; private set; }
public StringItem Username { get; private set; }
public StringItem Password { get; private set; }
public DisplayItem PagesWarning { get; private set; }
public StringItem Pages { get; private set; }
public DisplayItem SecurityWarning { get; private set; }
public BoolItem Latency { get; private set; }
public BoolItem Browser { get; private set; }
public DisplayItem LatencyWarning { get; private set; }
public StringItem LatencyStart { get; private set; }
public StringItem LatencyEnd { get; private set; }
public DisplayItem HeadersWarning { get; private set; }
public StringItem HeaderAccept { get; private set; }
public StringItem HeaderAcceptLang { get; private set; }
public BoolItem HeaderDNT { get; private set; }
public BoolItem HeaderUpgradeInsecure { get; private set; }
public StringItem HeaderUserAgent { get; private set; }
public DisplayItem DevWarning { get; private set; }
public BoolItem DevMode { get; private set; }
public BoolItem HardDriveCache { get; private set; }
public StringItem HardDriveCacheKeepTime { get; private set; }
public ConfigurationDataPhxBit()
: base()
{
PassKey = new HiddenItem { Name = "PassKey", Value = "" };
CredentialsWarning = new DisplayItem("<b>Credentials Configuration</b> (<i>Private Tracker</i>),<br /><br /> <ul><li><b>Username</b> is your account name on this tracker.</li><li><b>Password</b> is your password associated to your account name.</li></ul>") { Name = "Credentials" };
Username = new StringItem { Name = "Username (Required)", Value = "" };
Password = new StringItem { Name = "Password (Required)", Value = "" };
PagesWarning = new DisplayItem("<b>Preferences Configuration</b> (<i>Tweak your search settings</i>),<br /><br /> <ul><li><b>Max Pages to Process</b> let you specify how many page (max) Jackett can process when doing a search. Setting a value <b>higher than 4 is dangerous</b> for you account ! (<b>Result of too many requests to tracker...that <u>will be suspect</u></b>).</li></ul>") { Name = "Preferences" };
Pages = new StringItem { Name = "Max Pages to Process (Required)", Value = "4" };
SecurityWarning = new DisplayItem("<b>Security Configuration</b> (<i>Read this area carefully !</i>),<br /><br /> <ul><li><b>Latency Simulation</b> will simulate human browsing with Jacket by pausing Jacket for an random time between each request, to fake a real content browsing.</li><li><b>Browser Simulation</b> will simulate a real human browser by injecting additionals headers when doing requests to tracker.</li></ul>") { Name = "Security" };
Latency = new BoolItem() { Name = "Latency Simulation (Optional)", Value = false };
Browser = new BoolItem() { Name = "Browser Simulation (Optional)", Value = true };
LatencyWarning = new DisplayItem("<b>Latency Configuration</b> (<i>Required if latency simulation enabled</i>),<br /><br/> <ul><li>By filling this range, <b>Jackett will make a random timed pause</b> <u>between requests</u> to tracker <u>to simulate a real browser</u>.</li><li>MilliSeconds <b>only</b></li></ul>") { Name = "Simulate Latency" };
LatencyStart = new StringItem { Name = "Minimum Latency (ms)", Value = "1589" };
LatencyEnd = new StringItem { Name = "Maximum Latency (ms)", Value = "3674" };
HeadersWarning = new DisplayItem("<b>Browser Headers Configuration</b> (<i>Required if browser simulation enabled</i>),<br /><br /> <ul><li>By filling these fields, <b>Jackett will inject headers</b> with your values <u>to simulate a real browser</u>.</li><li>You can get <b>your browser values</b> here: <a href='https://www.whatismybrowser.com/detect/what-http-headers-is-my-browser-sending' target='blank'>www.whatismybrowser.com</a></li></ul><br /><i><b>Note that</b> some headers are not necessary because they are injected automatically by this provider such as Accept_Encoding, Connection, Host or X-Requested-With</i>") { Name = "Injecting headers" };
HeaderAccept = new StringItem { Name = "Accept", Value = "" };
HeaderAcceptLang = new StringItem { Name = "Accept-Language", Value = "" };
HeaderDNT = new BoolItem { Name = "DNT", Value = false };
HeaderUpgradeInsecure = new BoolItem { Name = "Upgrade-Insecure-Requests", Value = false };
HeaderUserAgent = new StringItem { Name = "User-Agent", Value = "" };
DevWarning = new DisplayItem("<b>Development Facility</b> (<i>For Developers ONLY</i>),<br /><br /> <ul><li>By enabling development mode, <b>Jackett will bypass his cache</b> and will <u>output debug messages to console</u> instead of his log file.</li><li>By enabling Hard Drive Cache, <b>This provider</b> will <u>save each query answers from tracker</u> in temp directory, in fact this reduce drastically HTTP requests when building a provider at parsing step for example. So, <b> Jackett will search for a cached query answer on hard drive before executing query on tracker side !</b> <i>DEV MODE must be enabled to use it !</li></ul>") { Name = "Development" };
DevMode = new BoolItem { Name = "Enable DEV MODE (Developers ONLY)", Value = false };
HardDriveCache = new BoolItem { Name = "Enable HARD DRIVE CACHE (Developers ONLY)", Value = false };
HardDriveCacheKeepTime = new StringItem { Name = "Keep Cached files for (ms)", Value = "300000" };
}
}
}

View File

@@ -0,0 +1,59 @@
namespace Jackett.Models.IndexerConfig.Bespoke
{
class ConfigurationDataWiHD : ConfigurationData
{
public DisplayItem CredentialsWarning { get; private set; }
public StringItem Username { get; private set; }
public StringItem Password { get; private set; }
public DisplayItem PagesWarning { get; private set; }
public StringItem Pages { get; private set; }
public BoolItem Exclusive { get; private set; }
public BoolItem Freeleech { get; private set; }
public BoolItem Reseed { get; private set; }
public DisplayItem SecurityWarning { get; private set; }
public BoolItem Latency { get; private set; }
public BoolItem Browser { get; private set; }
public DisplayItem LatencyWarning { get; private set; }
public StringItem LatencyStart { get; private set; }
public StringItem LatencyEnd { get; private set; }
public DisplayItem HeadersWarning { get; private set; }
public StringItem HeaderAccept { get; private set; }
public StringItem HeaderAcceptLang { get; private set; }
public BoolItem HeaderDNT { get; private set; }
public BoolItem HeaderUpgradeInsecure { get; private set; }
public StringItem HeaderUserAgent { get; private set; }
public DisplayItem DevWarning { get; private set; }
public BoolItem DevMode { get; private set; }
public BoolItem HardDriveCache { get; private set; }
public StringItem HardDriveCacheKeepTime { get; private set; }
public ConfigurationDataWiHD()
: base()
{
CredentialsWarning = new DisplayItem("<b>Credentials Configuration</b> (<i>Private Tracker</i>),<br /><br /> <ul><li><b>Username</b> is your account name on this tracker.</li><li><b>Password</b> is your password associated to your account name.</li></ul>") { Name = "Credentials" };
Username = new StringItem { Name = "Username (Required)", Value = "" };
Password = new StringItem { Name = "Password (Required)", Value = "" };
PagesWarning = new DisplayItem("<b>Preferences Configuration</b> (<i>Tweak your search settings</i>),<br /><br /> <ul><li><b>Max Pages to Process</b> let you specify how many page (max) Jackett can process when doing a search. Setting a value <b>higher than 4 is dangerous</b> for you account ! (<b>Result of too many requests to tracker...that <u>will be suspect</u></b>).</li><li><b>Exclusive Only</b> let you search <u>only</u> for torrents which are marked Exclusive.</li><li><b>Freeleech Only</b> let you search <u>only</u> for torrents which are marked Freeleech.</li><li><b>Reseed Only</b> let you search <u>only</u> for torrents which need to be seeded.</li></ul>") { Name = "Preferences" };
Pages = new StringItem { Name = "Max Pages to Process (Required)", Value = "4" };
Exclusive = new BoolItem() { Name = "Exclusive Only (Optional)", Value = false };
Freeleech = new BoolItem() { Name = "Freeleech Only (Optional)", Value = false };
Reseed = new BoolItem() { Name = "Reseed Needed Only (Optional)", Value = false };
SecurityWarning = new DisplayItem("<b>Security Configuration</b> (<i>Read this area carefully !</i>),<br /><br /> <ul><li><b>Latency Simulation</b> will simulate human browsing with Jacket by pausing Jacket for an random time between each request, to fake a real content browsing.</li><li><b>Browser Simulation</b> will simulate a real human browser by injecting additionals headers when doing requests to tracker.</li></ul>") { Name = "Security" };
Latency = new BoolItem() { Name = "Latency Simulation (Optional)", Value = true };
Browser = new BoolItem() { Name = "Browser Simulation (Optional)", Value = true };
LatencyWarning = new DisplayItem("<b>Latency Configuration</b> (<i>Required if latency simulation enabled</i>),<br /><br/> <ul><li>By filling this range, <b>Jackett will make a random timed pause</b> <u>between requests</u> to tracker <u>to simulate a real browser</u>.</li><li>MilliSeconds <b>only</b></li></ul>") { Name = "Simulate Latency" };
LatencyStart = new StringItem { Name = "Minimum Latency (ms)", Value = "1589" };
LatencyEnd = new StringItem { Name = "Maximum Latency (ms)", Value = "3674" };
HeadersWarning = new DisplayItem("<b>Browser Headers Configuration</b> (<i>Required if browser simulation enabled</i>),<br /><br /> <ul><li>By filling these fields, <b>Jackett will inject headers</b> with your values <u>to simulate a real browser</u>.</li><li>You can get <b>your browser values</b> here: <a href='https://www.whatismybrowser.com/detect/what-http-headers-is-my-browser-sending' target='blank'>www.whatismybrowser.com</a></li></ul><br /><i><b>Note that</b> some headers are not necessary because they are injected automatically by this provider such as Accept_Encoding, Connection, Host or X-Requested-With</i>") { Name = "Injecting headers" };
HeaderAccept = new StringItem { Name = "Accept", Value = "" };
HeaderAcceptLang = new StringItem { Name = "Accept-Language", Value = "" };
HeaderDNT = new BoolItem { Name = "DNT", Value = false };
HeaderUpgradeInsecure = new BoolItem { Name = "Upgrade-Insecure-Requests", Value = false };
HeaderUserAgent = new StringItem { Name = "User-Agent", Value = "" };
DevWarning = new DisplayItem("<b>Development Facility</b> (<i>For Developers ONLY</i>),<br /><br /> <ul><li>By enabling development mode, <b>Jackett will bypass his cache</b> and will <u>output debug messages to console</u> instead of his log file.</li><li>By enabling Hard Drive Cache, <b>This provider</b> will <u>save each query answers from tracker</u> in temp directory, in fact this reduce drastically HTTP requests when building a provider at parsing step for example. So, <b> Jackett will search for a cached query answer on hard drive before executing query on tracker side !</b> <i>DEV MODE must be enabled to use it !</li></ul>") { Name = "Development" };
DevMode = new BoolItem { Name = "Enable DEV MODE (Developers ONLY)", Value = false };
HardDriveCache = new BoolItem { Name = "Enable HARD DRIVE CACHE (Developers ONLY)", Value = false };
HardDriveCacheKeepTime = new StringItem { Name = "Keep Cached files for (ms)", Value = "300000" };
}
}
}

View File

@@ -0,0 +1,27 @@
namespace Jackett.Models.IndexerConfig.Bespoke
{
class ConfigurationDataXthor : ConfigurationData
{
public DisplayItem CredentialsWarning { get; private set; }
public StringItem PassKey { get; set; }
public DisplayItem PagesWarning { get; private set; }
public BoolItem Freeleech { get; private set; }
public DisplayItem DevWarning { get; private set; }
public BoolItem DevMode { get; private set; }
public BoolItem HardDriveCache { get; private set; }
public StringItem HardDriveCacheKeepTime { get; private set; }
public ConfigurationDataXthor()
: base()
{
CredentialsWarning = new DisplayItem("<b>Credentials Configuration</b> (<i>Private Tracker</i>),<br /><br /> <ul><li><b>PassKey</b> is your private key on your account</li></ul>") { Name = "Credentials" };
PassKey = new StringItem { Name = "PassKey", Value = "" };
PagesWarning = new DisplayItem("<b>Preferences Configuration</b> (<i>Tweak your search settings</i>),<br /><br /> <ul><li><b>Freeleech Only</b> let you search <u>only</u> for torrents which are marked Freeleech.</li></ul>") { Name = "Preferences" };
Freeleech = new BoolItem() { Name = "Freeleech Only (Optional)", Value = false };
DevWarning = new DisplayItem("<b>Development Facility</b> (<i>For Developers ONLY</i>),<br /><br /> <ul><li>By enabling development mode, <b>Jackett will bypass his cache</b> and will <u>output debug messages to console</u> instead of his log file.</li><li>By enabling Hard Drive Cache, <b>This provider</b> will <u>save each query answers from tracker</u> in temp directory, in fact this reduce drastically HTTP requests when building a provider at parsing step for example. So, <b> Jackett will search for a cached query answer on hard drive before executing query on tracker side !</b> <i>DEV MODE must be enabled to use it !</li></ul>") { Name = "Development" };
DevMode = new BoolItem { Name = "Enable DEV MODE (Developers ONLY)", Value = false };
HardDriveCache = new BoolItem { Name = "Enable HARD DRIVE CACHE (Developers ONLY)", Value = false };
HardDriveCacheKeepTime = new StringItem { Name = "Keep Cached files for (ms)", Value = "300000" };
}
}
}

View File

@@ -132,7 +132,7 @@ namespace Jackett.Models.IndexerConfig
if (!forDisplay)
{
properties = properties
.Where(p => p.ItemType == ItemType.HiddenData || p.ItemType == ItemType.InputBool || p.ItemType == ItemType.InputString || p.ItemType == ItemType.Recaptcha)
.Where(p => p.ItemType == ItemType.HiddenData || p.ItemType == ItemType.InputBool || p.ItemType == ItemType.InputString || p.ItemType == ItemType.Recaptcha || p.ItemType == ItemType.DisplayInfo)
.ToArray();
}

View File

@@ -11,11 +11,13 @@ namespace Jackett.Models.IndexerConfig
{
public StringItem Username { get; private set; }
public StringItem Password { get; private set; }
public DisplayItem Instructions { get; private set; }
public ConfigurationDataBasicLogin()
public ConfigurationDataBasicLogin(string instructionMessageOptional = null)
{
Username = new StringItem { Name = "Username" };
Password = new StringItem { Name = "Password" };
Instructions = new DisplayItem(instructionMessageOptional) { Name = "" };
}

View File

@@ -0,0 +1,27 @@
using Newtonsoft.Json.Linq;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Jackett.Models.IndexerConfig
{
public class ConfigurationDataBasicLoginWithAlternateLink : ConfigurationData
{
public StringItem Username { get; private set; }
public StringItem Password { get; private set; }
public DisplayItem Instructions { get; private set; }
public StringItem AlternateLink { get; set; }
public ConfigurationDataBasicLoginWithAlternateLink(string instructionMessageOptional = null)
{
Username = new StringItem { Name = "Username" };
Password = new StringItem { Name = "Password" };
Instructions = new DisplayItem(instructionMessageOptional) { Name = "" };
AlternateLink = new StringItem { Name = "Alternate Link" };
}
}
}

View File

@@ -11,6 +11,7 @@ namespace Jackett.Models.IndexerConfig
{
public StringItem Username { get; private set; }
public StringItem Password { get; private set; }
public HiddenItem LastLoggedInCheck { get; private set; }
public DisplayItem FilterExample { get; private set; }
public StringItem FilterString { get; private set; }
@@ -18,6 +19,7 @@ namespace Jackett.Models.IndexerConfig
{
Username = new StringItem { Name = "Username" };
Password = new StringItem { Name = "Password" };
LastLoggedInCheck = new HiddenItem { Name = "LastLoggedInCheck" };
FilterExample = new DisplayItem(FilterInstructions)
{
Name = ""

View File

@@ -0,0 +1,32 @@
using Newtonsoft.Json.Linq;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Jackett.Models.IndexerConfig
{
public class ConfigurationDataBasicLoginWithFilterAndPasskey : ConfigurationData
{
public StringItem Username { get; private set; }
public StringItem Password { get; private set; }
public StringItem Passkey { get; private set; }
public DisplayItem FilterExample { get; private set; }
public StringItem FilterString { get; private set; }
public ConfigurationDataBasicLoginWithFilterAndPasskey(string FilterInstructions)
{
Username = new StringItem { Name = "Username" };
Password = new StringItem { Name = "Password" };
Passkey = new StringItem { Name = "Passkey" };
FilterExample = new DisplayItem(FilterInstructions)
{
Name = ""
};
FilterString = new StringItem { Name = "Filters (optional)" };
}
}
}

View File

@@ -0,0 +1,25 @@
using Newtonsoft.Json.Linq;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Jackett.Models.IndexerConfig
{
public class ConfigurationDataBasicLoginWithRSSAndDisplay : ConfigurationData
{
public StringItem Username { get; private set; }
public StringItem Password { get; private set; }
public HiddenItem RSSKey { get; private set; }
public DisplayItem DisplayText { get; private set; }
public ConfigurationDataBasicLoginWithRSSAndDisplay()
{
Username = new StringItem { Name = "Username" };
Password = new StringItem { Name = "Password" };
RSSKey = new HiddenItem { Name = "RSSKey" };
DisplayText = new DisplayItem(""){ Name = "" };
}
}
}

View File

@@ -18,13 +18,17 @@ namespace Jackett.Models.IndexerConfig
public HiddenItem CaptchaCookie { get; private set; }
public ConfigurationDataCaptchaLogin()
public DisplayItem Instructions { get; private set; }
/// <param name="instructionMessageOptional">Enter any instructions the user will need to setup the tracker</param>
public ConfigurationDataCaptchaLogin(string instructionMessageOptional = null)
{
Username = new StringItem { Name = "Username" };
Password = new StringItem { Name = "Password" };
CaptchaImage = new ImageItem { Name = "Captcha Image" };
CaptchaText = new StringItem { Name = "Captcha Text" };
CaptchaCookie = new HiddenItem("") { Name = "Captcha Cookie" };
Instructions = new DisplayItem(instructionMessageOptional) { Name = "" };
}
}
}

View File

@@ -18,6 +18,7 @@ namespace Jackett.Models
public int Limit { get; set; }
public int Offset { get; set; }
public int RageID { get; set; }
public string ImdbID { get; set; }
public int Season { get; set; }
public string Episode { get; set; }

View File

@@ -213,6 +213,11 @@ namespace Jackett.Services
/// <returns></returns>
public static string GetAppDataFolderStatic()
{
if (!string.IsNullOrWhiteSpace(Startup.CustomDataFolder))
{
return Startup.CustomDataFolder;
}
if (System.Environment.OSVersion.Platform == PlatformID.Unix)
{
return Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData), "Jackett");

View File

@@ -6,6 +6,7 @@ using System.Reflection;
using System.Security.Cryptography;
using System.Text;
using System.Threading.Tasks;
using Jackett.Utils;
namespace Jackett.Services
{
@@ -18,6 +19,7 @@ namespace Jackett.Services
public class ProtectionService : IProtectionService
{
DataProtectionScope PROTECTION_SCOPE = DataProtectionScope.LocalMachine;
private const string JACKETT_KEY = "JACKETT_KEY";
const string APPLICATION_KEY = "Dvz66r3n8vhTGip2/quiw5ISyM37f7L2iOdupzdKmzkvXGhAgQiWK+6F+4qpxjPVNks1qO7LdWuVqRlzgLzeW8mChC6JnBMUS1Fin4N2nS9lh4XPuCZ1che75xO92Nk2vyXUo9KSFG1hvEszAuLfG2Mcg1r0sVyVXd2gQDU/TbY=";
IServerService serverService;
@@ -34,6 +36,34 @@ namespace Jackett.Services
}
public string Protect(string plainText)
{
var jackettKey = Environment.GetEnvironmentVariable(JACKETT_KEY);
if (jackettKey == null)
{
return ProtectDefaultMethod(plainText);
}
else
{
return ProtectUsingKey(plainText, jackettKey);
}
}
public string UnProtect(string plainText)
{
var jackettKey = Environment.GetEnvironmentVariable(JACKETT_KEY);
if (jackettKey == null)
{
return UnProtectDefaultMethod(plainText);
}
else
{
return UnProtectUsingKey(plainText, jackettKey);
}
}
private string ProtectDefaultMethod(string plainText)
{
if (string.IsNullOrEmpty(plainText))
return string.Empty;
@@ -72,7 +102,7 @@ namespace Jackett.Services
return Convert.ToBase64String(protectedBytes);
}
public string UnProtect(string plainText)
private string UnProtectDefaultMethod(string plainText)
{
if (string.IsNullOrEmpty(plainText))
return string.Empty;
@@ -111,6 +141,16 @@ namespace Jackett.Services
return Encoding.UTF8.GetString(unprotectedBytes);
}
private string ProtectUsingKey(string plainText, string key)
{
return StringCipher.Encrypt(plainText, key);
}
private string UnProtectUsingKey(string plainText, string key)
{
return StringCipher.Decrypt(plainText, key);
}
public void Protect<T>(T obj)
{
var type = obj.GetType();

View File

@@ -19,6 +19,7 @@ using System.Net;
using System.Net.NetworkInformation;
using System.Security.Cryptography;
using System.Text;
using System.Text.RegularExpressions;
using System.Threading.Tasks;
using System.Web;
@@ -32,7 +33,8 @@ namespace Jackett.Services
void ReserveUrls(bool doInstall = true);
ServerConfig Config { get; }
void SaveConfig();
Uri ConvertToProxyLink(Uri link, string serverUrl, string indexerId, string action = "dl", string file = "t.torrent");
Uri ConvertToProxyLink(Uri link, string serverUrl, string indexerId, string action = "dl", string file = "t.torrent");
string BasePath();
}
public class ServerService : IServerService
@@ -73,10 +75,28 @@ namespace Jackett.Services
return link;
var encodedLink = HttpServerUtility.UrlTokenEncode(Encoding.UTF8.GetBytes(link.ToString()));
var proxyLink = string.Format("{0}{1}/{2}/{3}?path={4}&file={5}", serverUrl, action, indexerId, config.APIKey, encodedLink, file);
string urlEncodedFile = WebUtility.UrlEncode(file);
var proxyLink = string.Format("{0}{1}/{2}/{3}?path={4}&file={5}", serverUrl, action, indexerId, config.APIKey, encodedLink, urlEncodedFile);
return new Uri(proxyLink);
}
public string BasePath()
{
if (config.BasePathOverride == null || config.BasePathOverride == "") {
return "/";
}
var path = config.BasePathOverride;
if (!path.EndsWith("/"))
{
path = path + "/";
}
if (!path.StartsWith("/"))
{
path = "/" + path;
}
return path;
}
private void LoadConfig()
{
// Load config
@@ -139,6 +159,7 @@ namespace Jackett.Services
logger.Info("Starting web server at " + config.GetListenAddresses()[0]);
var startOptions = new StartOptions();
config.GetListenAddresses().ToList().ForEach(u => startOptions.Urls.Add(u));
Startup.BasePath = BasePath();
_server = WebApp.Start<Startup>(startOptions);
logger.Debug("Web server started");
updater.StartUpdateChecker();

View File

@@ -109,6 +109,11 @@ namespace Jackett.Services
var releases = JsonConvert.DeserializeObject<List<Release>>(response.Content);
if (!config.UpdatePrerelease)
{
releases = releases.Where(r => !r.Prerelease).ToList();
}
if (releases.Count > 0)
{
var latestRelease = releases.OrderByDescending(o => o.Created_at).First();

View File

@@ -1,4 +1,4 @@
using Owin;
using Owin;
using System;
using System.Collections.Generic;
using System.Linq;
@@ -40,6 +40,12 @@ namespace Jackett
set;
}
public static string ProxyConnection
{
get;
set;
}
public static bool? DoSSLFix
{
get;
@@ -52,6 +58,18 @@ namespace Jackett
set;
}
public static string CustomDataFolder
{
get;
set;
}
public static string BasePath
{
get;
set;
}
public void Configuration(IAppBuilder appBuilder)
{
// Configure Web API for self-host.

View File

@@ -13,11 +13,11 @@ namespace Jackett.Utils
get {
if (System.Environment.OSVersion.Platform == PlatformID.Unix)
{
return "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Ubuntu Chrome/45.0.2454.101 Safari/537.36";
return "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Ubuntu Chrome/47.0.2526.73 Safari/537.36";
}
else
{
return "Mozilla/5.0 (Windows NT 6.3; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/45.0.2454.101 Safari/537.36";
return "Mozilla/5.0 (Windows NT 6.3; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/47.0.2526.73 Safari/537.36";
}
}
}

View File

@@ -46,6 +46,8 @@ namespace Jackett.Utils.Clients
private async Task<WebClientByteResult> Run(WebRequest webRequest)
{
ServicePointManager.SecurityProtocol = (SecurityProtocolType)192 | (SecurityProtocolType)768 | (SecurityProtocolType)3072;
var cookies = new CookieContainer();
if (!string.IsNullOrEmpty(webRequest.Cookies))
{
@@ -62,18 +64,28 @@ namespace Jackett.Utils.Clients
}
}
}
var useProxy = false;
WebProxy proxyServer = null;
if (Startup.ProxyConnection != null)
{
proxyServer = new WebProxy(Startup.ProxyConnection, false);
useProxy = true;
}
var client = new HttpClient(new HttpClientHandler
{
CookieContainer = cookies,
AllowAutoRedirect = false, // Do not use this - Bugs ahoy! Lost cookies and more.
UseCookies = true,
Proxy = proxyServer,
UseProxy = useProxy
});
if(webRequest.EmulateBrowser)
client.DefaultRequestHeaders.Add("User-Agent", BrowserUtil.ChromeUserAgent);
if (webRequest.EmulateBrowser)
client.DefaultRequestHeaders.Add("User-Agent", BrowserUtil.ChromeUserAgent);
else
client.DefaultRequestHeaders.Add("User-Agent", "Jackett/" + configService.GetVersion());
client.DefaultRequestHeaders.Add("User-Agent", "Jackett/" + configService.GetVersion());
HttpResponseMessage response = null;
var request = new HttpRequestMessage();
request.Headers.ExpectContinue = false;
@@ -118,6 +130,35 @@ namespace Jackett.Utils.Clients
var result = new WebClientByteResult();
result.Content = await response.Content.ReadAsByteArrayAsync();
// some cloudflare clients are using a refresh header
// Pull it out manually
if (response.StatusCode == System.Net.HttpStatusCode.ServiceUnavailable && response.Headers.Contains("Refresh"))
{
var refreshHeaders = response.Headers.GetValues("Refresh");
var redirval = "";
var redirtime = 0;
if (refreshHeaders != null)
{
foreach (var value in refreshHeaders)
{
var start = value.IndexOf("=");
var end = value.IndexOf(";");
var len = value.Length;
if (start > -1)
{
redirval = value.Substring(start + 1);
result.RedirectingTo = redirval;
// normally we don't want a serviceunavailable (503) to be a redirect, but that's the nature
// of this cloudflare approach..don't want to alter BaseWebResult.IsRedirect because normally
// it shoudln't include service unavailable..only if we have this redirect header.
response.StatusCode = System.Net.HttpStatusCode.Redirect;
redirtime = Int32.Parse(value.Substring(0, end));
System.Threading.Thread.Sleep(redirtime * 1000);
}
}
}
}
if (response.Headers.Location != null)
{
result.RedirectingTo = response.Headers.Location.ToString();
@@ -129,6 +170,7 @@ namespace Jackett.Utils.Clients
// http://stackoverflow.com/questions/14681144/httpclient-not-storing-cookies-in-cookiecontainer
IEnumerable<string> cookieHeaders;
var responseCookies = new List<Tuple<string, string>>();
if (response.Headers.TryGetValues("set-cookie", out cookieHeaders))
{
foreach (var value in cookieHeaders)

View File

@@ -104,6 +104,28 @@ namespace Jackett.Utils.Clients
case "location":
result.RedirectingTo = header[1];
break;
case "refresh":
if (response.Status == System.Net.HttpStatusCode.ServiceUnavailable)
{
//"Refresh: 8;URL=/cdn-cgi/l/chk_jschl?pass=1451000679.092-1vJFUJLb9R"
var redirval = "";
var value = header[1];
var start = value.IndexOf("=");
var end = value.IndexOf(";");
var len = value.Length;
if (start > -1)
{
redirval = value.Substring(start + 1);
result.RedirectingTo = redirval;
// normally we don't want a serviceunavailable (503) to be a redirect, but that's the nature
// of this cloudflare approach..don't want to alter BaseWebResult.IsRedirect because normally
// it shoudln't include service unavailable..only if we have this redirect header.
result.Status = System.Net.HttpStatusCode.Redirect;
var redirtime = Int32.Parse(value.Substring(0, end));
System.Threading.Thread.Sleep(redirtime * 1000);
}
}
break;
}
}
}

View File

@@ -50,6 +50,11 @@ namespace Jackett.Utils.Clients
private async Task<WebClientByteResult> Run(WebRequest request)
{
var args = new StringBuilder();
if (Startup.ProxyConnection != null)
{
args.AppendFormat("-x " + Startup.ProxyConnection + " ");
}
args.AppendFormat("--url \"{0}\" ", request.Url);
if (request.EmulateBrowser)
@@ -86,11 +91,16 @@ namespace Jackett.Utils.Clients
// https://git.fedorahosted.org/cgit/mod_nss.git/plain/docs/mod_nss.html
args.Append("--cipher " + SSLFix.CipherList);
}
if (Startup.IgnoreSslErrors == true)
{
args.Append("-k ");
}
args.Append("-H \"Accept-Language: en-US,en\" ");
args.Append("-H \"Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8\" ");
string stdout = null;
await Task.Run(() =>
{
stdout = processService.StartProcessAndGetOutput(System.Environment.OSVersion.Platform == PlatformID.Unix ? "curl" : "curl.exe", args.ToString(), true);
stdout = processService.StartProcessAndGetOutput(System.Environment.OSVersion.Platform == PlatformID.Unix ? "curl" : "curl.exe", args.ToString() , true);
});
var outputData = File.ReadAllBytes(tempFile);
@@ -101,6 +111,16 @@ namespace Jackett.Utils.Clients
if (headSplit < 0)
throw new Exception("Invalid response");
var headers = stdout.Substring(0, headSplit);
if (Startup.ProxyConnection != null)
{
// the proxy provided headers too so we need to split headers again
var headSplit1 = stdout.IndexOf("\r\n\r\n",headSplit + 4);
if (headSplit1 > 0)
{
headers = stdout.Substring(headSplit + 4,headSplit1 - (headSplit + 4));
headSplit = headSplit1;
}
}
var headerCount = 0;
var cookieBuilder = new StringBuilder();
var cookies = new List<Tuple<string, string>>();
@@ -131,6 +151,24 @@ namespace Jackett.Utils.Clients
case "location":
result.RedirectingTo = value.Trim();
break;
case "refresh":
//"Refresh: 8;URL=/cdn-cgi/l/chk_jschl?pass=1451000679.092-1vJFUJLb9R"
var redirval = "";
var start = value.IndexOf("=");
var end = value.IndexOf(";");
var len = value.Length;
if (start > -1)
{
redirval = value.Substring(start + 1);
result.RedirectingTo = redirval;
// normally we don't want a serviceunavailable (503) to be a redirect, but that's the nature
// of this cloudflare approach..don't want to alter BaseWebResult.IsRedirect because normally
// it shoudln't include service unavailable..only if we have this redirect header.
result.Status = System.Net.HttpStatusCode.Redirect;
var redirtime = Int32.Parse(value.Substring(0, end));
System.Threading.Thread.Sleep(redirtime * 1000);
}
break;
}
}
}

View File

@@ -0,0 +1,106 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Security.Cryptography;
using System.Text;
using System.Threading.Tasks;
namespace Jackett.Utils
{
public static class StringCipher
{
// This constant is used to determine the keysize of the encryption algorithm in bits.
// We divide this by 8 within the code below to get the equivalent number of bytes.
private const int Keysize = 256;
// This constant determines the number of iterations for the password bytes generation function.
private const int DerivationIterations = 1000;
public static string Encrypt(string plainText, string passPhrase)
{
// Salt and IV is randomly generated each time, but is preprended to encrypted cipher text
// so that the same Salt and IV values can be used when decrypting.
var saltStringBytes = Generate256BitsOfRandomEntropy();
var ivStringBytes = Generate256BitsOfRandomEntropy();
var plainTextBytes = Encoding.UTF8.GetBytes(plainText);
using (var password = new Rfc2898DeriveBytes(passPhrase, saltStringBytes, DerivationIterations))
{
var keyBytes = password.GetBytes(Keysize / 8);
using (var symmetricKey = new RijndaelManaged())
{
symmetricKey.BlockSize = 256;
symmetricKey.Mode = CipherMode.CBC;
symmetricKey.Padding = PaddingMode.PKCS7;
using (var encryptor = symmetricKey.CreateEncryptor(keyBytes, ivStringBytes))
{
using (var memoryStream = new MemoryStream())
{
using (var cryptoStream = new CryptoStream(memoryStream, encryptor, CryptoStreamMode.Write))
{
cryptoStream.Write(plainTextBytes, 0, plainTextBytes.Length);
cryptoStream.FlushFinalBlock();
// Create the final bytes as a concatenation of the random salt bytes, the random iv bytes and the cipher bytes.
var cipherTextBytes = saltStringBytes;
cipherTextBytes = cipherTextBytes.Concat(ivStringBytes).ToArray();
cipherTextBytes = cipherTextBytes.Concat(memoryStream.ToArray()).ToArray();
memoryStream.Close();
cryptoStream.Close();
return Convert.ToBase64String(cipherTextBytes);
}
}
}
}
}
}
public static string Decrypt(string cipherText, string passPhrase)
{
// Get the complete stream of bytes that represent:
// [32 bytes of Salt] + [32 bytes of IV] + [n bytes of CipherText]
var cipherTextBytesWithSaltAndIv = Convert.FromBase64String(cipherText);
// Get the saltbytes by extracting the first 32 bytes from the supplied cipherText bytes.
var saltStringBytes = cipherTextBytesWithSaltAndIv.Take(Keysize / 8).ToArray();
// Get the IV bytes by extracting the next 32 bytes from the supplied cipherText bytes.
var ivStringBytes = cipherTextBytesWithSaltAndIv.Skip(Keysize / 8).Take(Keysize / 8).ToArray();
// Get the actual cipher text bytes by removing the first 64 bytes from the cipherText string.
var cipherTextBytes = cipherTextBytesWithSaltAndIv.Skip((Keysize / 8) * 2).Take(cipherTextBytesWithSaltAndIv.Length - ((Keysize / 8) * 2)).ToArray();
using (var password = new Rfc2898DeriveBytes(passPhrase, saltStringBytes, DerivationIterations))
{
var keyBytes = password.GetBytes(Keysize / 8);
using (var symmetricKey = new RijndaelManaged())
{
symmetricKey.BlockSize = 256;
symmetricKey.Mode = CipherMode.CBC;
symmetricKey.Padding = PaddingMode.PKCS7;
using (var decryptor = symmetricKey.CreateDecryptor(keyBytes, ivStringBytes))
{
using (var memoryStream = new MemoryStream(cipherTextBytes))
{
using (var cryptoStream = new CryptoStream(memoryStream, decryptor, CryptoStreamMode.Read))
{
var plainTextBytes = new byte[cipherTextBytes.Length];
var decryptedByteCount = cryptoStream.Read(plainTextBytes, 0, plainTextBytes.Length);
memoryStream.Close();
cryptoStream.Close();
return Encoding.UTF8.GetString(plainTextBytes, 0, decryptedByteCount);
}
}
}
}
}
}
private static byte[] Generate256BitsOfRandomEntropy()
{
var randomBytes = new byte[32]; // 32 Bytes will give us 256 bits.
using (var rngCsp = new RNGCryptoServiceProvider())
{
// Fill the array with cryptographically secure random bytes.
rngCsp.GetBytes(randomBytes);
}
return randomBytes;
}
}
}

View File

@@ -74,7 +74,7 @@ namespace Jackett.Utils
// Filter out releases that do have a valid imdb ID, that is not equal to the one we're searching for.
return
results.Where(
result => !result.Imdb.HasValue || result.Imdb.Value == 0 || ("tt" + result.Imdb.Value).Equals(imdb));
result => !result.Imdb.HasValue || result.Imdb.Value == 0 || ("tt" + result.Imdb.Value.ToString("D7")).Equals(imdb));
}
private static string CleanTitle(string title)

View File

@@ -1,32 +1,42 @@
using Microsoft.Owin;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Jackett.Utils
{
public class WebApiRootRedirectMiddleware : OwinMiddleware
{
public WebApiRootRedirectMiddleware(OwinMiddleware next)
: base(next)
{
}
public async override Task Invoke(IOwinContext context)
{
var url = context.Request.Uri;
if (string.IsNullOrWhiteSpace(url.AbsolutePath) || url.AbsolutePath == "/")
{
// 301 is the status code of permanent redirect
context.Response.StatusCode = 301;
context.Response.Headers.Set("Location", "/Admin/Dashboard");
}
else
{
await Next.Invoke(context);
}
}
}
using Jackett.Services;
using Microsoft.Owin;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Jackett.Utils
{
public class WebApiRootRedirectMiddleware : OwinMiddleware
{
public WebApiRootRedirectMiddleware(OwinMiddleware next)
: base(next)
{
}
public async override Task Invoke(IOwinContext context)
{
var url = context.Request.Uri;
if(context.Request.Path != null && context.Request.Path.HasValue && context.Request.Path.Value.StartsWith(Startup.BasePath))
{
context.Request.Path = new PathString(context.Request.Path.Value.Substring(Startup.BasePath.Length-1));
}
if (string.IsNullOrWhiteSpace(url.AbsolutePath) || url.AbsolutePath == "/")
{
// 301 is the status code of permanent redirect
context.Response.StatusCode = 302;
var redir = Startup.BasePath + "Admin/Dashboard";
Engine.Logger.Info("redirecting to " + redir);
context.Response.Headers.Set("Location", redir);
}
else
{
await Next.Invoke(context);
}
}
}
}

View File

@@ -1,5 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<packages>
<package id="AngleSharp" version="0.9.5" targetFramework="net45" />
<package id="Autofac" version="3.5.2" targetFramework="net45" />
<package id="Autofac.Owin" version="3.1.0" targetFramework="net45" />
<package id="Autofac.WebApi" version="3.1.0" targetFramework="net45" />